GuidesMarketing campaigns

Set up marketing campaigns

This guide walks through building a drip campaign, saving a reusable audience segment, and enrolling shoppers into it. Everything here uses your merchant credential (Authorization: Bearer fdk_...); the tenant is resolved from the key, so you never pass it in a path or body. All routes require the manage:own_marketing scope and are mounted under /v1/merchant/marketing.

Sending itself is automatic. A background drip runner sweeps due enrollments, sends each step through the platform’s mail transport, appends the CAN-SPAM footer (unsubscribe link plus postal address), honors suppression and marketing opt-outs, and advances exactly once per step. You author the campaign and its audience; the platform delivers it.

Before you start

  • A merchant credential with manage:own_marketing.
  • The base URL: https://api.ruo.pro (or http://localhost:3000 locally).
  • Error responses are always nested: { "error": { "code": "...", "message": "..." } }.

Build and enroll a campaign

Save a segment (optional, but reusable)

A segment is a named, saved audience. Its query is a predicate tree over an allow-listed set of fields — email, name, created_at, order_count, lifetime_cents, last_order_at, subscription_status, marketing_opted_in, plus the engagement fields last_open_at, last_click_at, and enrolled_in_campaign. Leaf operators are eq, neq, gt, gte, lt, lte, in, nin, contains, starts_with, and exists; branches are and / or.

curl -X POST https://api.ruo.pro/v1/merchant/marketing/segments \
  -H "Authorization: Bearer fdk_your_key_here" \
  -H "content-type: application/json" \
  -d '{
    "name": "Repeat buyers",
    "query": { "predicate": {
      "op": "and",
      "children": [
        { "field": "order_count", "op": "gte", "value": 2 },
        { "field": "marketing_opted_in", "op": "eq", "value": true }
      ]
    } }
  }'

Check its size before you send anything — this reads live, never a snapshot:

curl https://api.ruo.pro/v1/merchant/marketing/segments/{id}/count \
  -H "Authorization: Bearer fdk_your_key_here"

Create the campaign

A campaign has a kind (drip, broadcast, or abandoned_cart), a required audience_query, and an ordered list of steps. There is no implicit “everyone” — state the audience explicitly as a { "predicate": ... }, a { "segment_id": "..." } reference, or a legacy { "type": ... } shape.

Each step names a template_ref. A store campaign may send the merchant-sendable templates only — welcome, abandoned-cart, and abandoned-cart-reminder — or one of your own tenant templates as tenant:<version_id>. Internal platform mail (receipts, magic links, dunning) is refused with a 403 template_not_sendable.

curl -X POST https://api.ruo.pro/v1/merchant/marketing/campaigns \
  -H "Authorization: Bearer fdk_your_key_here" \
  -H "content-type: application/json" \
  -d '{
    "name": "Welcome series",
    "kind": "drip",
    "audience_query": { "segment_id": "seg_..." },
    "exit_on_order": true,
    "steps": [
      { "delay_seconds": 0,     "template_ref": "welcome" },
      { "delay_seconds": 259200, "template_ref": "abandoned-cart-reminder" }
    ]
  }'
⚠️

Any from_address you supply on a merchant step is ignored. Every step sends from your store’s default sender (no-reply@ your verified sender domain, else the platform sender). This is enforced, not a default you can override.

Enroll the audience

Resolve the campaign’s audience through the customer data platform and enroll every match at step 0. Enrollment is idempotent per person, so a re-run only picks up newcomers — it never re-sends to someone already enrolled.

curl -X POST https://api.ruo.pro/v1/merchant/marketing/campaigns/{id}/enroll \
  -H "Authorization: Bearer fdk_your_key_here"

abandoned_cart campaigns are the exception: they are not enrolled this way. The storefront signals cart activity server-side, and an idle-cart sweep enrolls shoppers into your active abandoned-cart campaign automatically.

Watch it run, and control it

List enrollments (each row carries current_step, status, and a halt_reason when a step’s template is misconfigured), and read the PII-free daily engagement rollup:

curl https://api.ruo.pro/v1/merchant/marketing/campaigns/{id}/enrollments \
  -H "Authorization: Bearer fdk_your_key_here"
 
curl "https://api.ruo.pro/v1/merchant/marketing/campaigns/{id}/engagement-daily?days=30" \
  -H "Authorization: Bearer fdk_your_key_here"

Pause, resume, or cancel at any time. Pausing stops the drip without losing anyone’s place; resuming continues from exactly where each enrollment stood.

curl -X POST https://api.ruo.pro/v1/merchant/marketing/campaigns/{id}/pause \
  -H "Authorization: Bearer fdk_your_key_here"

Trigger a campaign from an event

Instead of a one-time enroll, attach a trigger so people enter as an event fires. The trigger vocabulary is closed: commerce.order.placed.v1, person.created.v1, and subscription.cancelled.v1. An optional filter_query narrows who enters.

curl -X POST https://api.ruo.pro/v1/merchant/marketing/campaigns/{id}/triggers \
  -H "Authorization: Bearer fdk_your_key_here" \
  -H "content-type: application/json" \
  -d '{ "event_name": "person.created.v1", "once_per_person": true }'

What is automatic vs. what you configure

  • You configure: campaigns, steps, segments, triggers, tenant templates, and lifecycle (pause/resume/cancel).
  • The platform handles automatically: due-step sending, exactly-once delivery, the CAN-SPAM footer, suppression and marketing-consent checks, per-day send caps and quiet hours, bounce ingestion, and abandoned-cart detection.