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(orhttp://localhost:3000locally). - 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.
Related
- Marketing reference — the full route and event map.
- Quickstart — your first authenticated request.