Skip to content

Campaign from a saved list ​

Send to an audience already in the platform — the pattern for a recurring send to a stable list.

Permissions needed: templates:read, lists:read, campaigns:create, campaigns:read.

1. Find the list ​

bash
curl -s -H "x-api-key: $DRSE_API_KEY" "$DRSE_BASE_URL/api/v1/lists"
ts
const res = await fetch(`${BASE}/api/v1/lists`, {
  headers: { "x-api-key": KEY },
});
const { data, meta } = await res.json();
console.log(`${meta.listCount} lists`);
json
{
  "data": [
    { "id": "550e8400-e29b-41d4-a716-446655440001", "name": "Summer list",
      "createdAt": "2026-07-25T09:00:00.000Z", "updatedAt": "2026-07-25T09:30:00.000Z",
      "recipientCount": 250 }
  ],
  "meta": { "totalRecipientCount": 250, "listCount": 1 }
}

Check recipientCount before sending

A campaign against an empty list fails with 422 list_empty. Checking here makes that a pre-flight rather than a failed create.

2. Confirm the template ​

bash
curl -s -H "x-api-key: $DRSE_API_KEY" \
  "$DRSE_BASE_URL/api/v1/templates?limit=100"
ts
const res = await fetch(`${BASE}/api/v1/templates?limit=100`, {
  headers: { "x-api-key": KEY },
});
const { data } = await res.json();

Store the id in configuration rather than matching on name at send time — a rename in the web app would silently break sending, or match the wrong template. See Templates.

3. Create the campaign ​

bash
curl -X POST "$DRSE_BASE_URL/api/v1/campaigns" \
  -H "x-api-key: $DRSE_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Summer promo — July",
    "templateId": "550e8400-e29b-41d4-a716-446655440002",
    "listId": "550e8400-e29b-41d4-a716-446655440001"
  }'
ts
const res = await fetch(`${BASE}/api/v1/campaigns`, {
  method: "POST",
  headers: {
    "x-api-key": KEY,
    "Idempotency-Key": idempotencyKey, // persisted before this call
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "Summer promo — July",
    templateId: "550e8400-e29b-41d4-a716-446655440002",
    listId: "550e8400-e29b-41d4-a716-446655440001",
  }),
});

const body = await res.json();
if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
const campaignId = body.data.id;

201 Created

json
{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Summer promo — July",
    "status": "pending",
    "progress": 0,
    "listId": "550e8400-e29b-41d4-a716-446655440001",
    "listName": "Summer list",
    "templateId": "018f2c7a-0000-7000-8000-000000000000",
    "publishedAt": null,
    "createdAt": "2026-07-25T10:00:00.000Z",
    "updatedAt": "2026-07-25T10:00:00.000Z",
    "qrCode": null
  }
}

No import field: this call did not create recipients, it reused a list.

4. Poll to completion ​

ts
async function waitFor(campaignId: string) {
  let delay = 2000;
  for (;;) {
    const res = await fetch(`${BASE}/api/v1/campaigns/${campaignId}`, {
      headers: { "x-api-key": KEY },
    });
    const { data } = await res.json();

    if (data.status === "completed") return data;
    if (data.status === "failed") {
      throw new Error(`Campaign ${campaignId} failed at ${data.progress}%`);
    }

    await new Promise((r) => setTimeout(r, delay));
    delay = Math.min(delay * 2, 30_000);
  }
}

See Polling & failures.

Adding a QR code ​

Include it in the create call so the code exists before rendering starts:

json
{
  "name": "Summer promo — July",
  "templateId": "550e8400-e29b-41d4-a716-446655440002",
  "listId": "550e8400-e29b-41d4-a716-446655440001",
  "qrCode": {
    "url": "https://example.com/summer",
    "description": "Summer landing page",
    "trackingEnabled": true
  }
}

The response's qrCode then carries the slug and trackingUrl. See Track QR scans.

Overriding template details ​

Per campaign, without editing the template:

json
{
  "name": "Summer promo — July",
  "templateId": "550e8400-e29b-41d4-a716-446655440002",
  "listId": "550e8400-e29b-41d4-a716-446655440001",
  "returnAddress": "Acme AB, Box 42, 111 22 Stockholm",
  "skipTemplateLogo": true
}

Full list: Campaigns.

Failure modes ​

Statuscode / messageFix
404List not foundThe listId is wrong or belongs to another account
404Template not foundThe templateId is wrong, or you passed a campaign's snapshot id
422list_empty — List has no recipientsAdd recipients first
409List no longer existsDeleted mid-request, possibly by 90-day retention
403forbiddenThe key is missing campaigns:create or lists:read

Retention can empty a stable list

A list untouched for 90 days is deleted. A quarterly send is right at that edge — if the send is the only activity, the list may vanish between runs. Keep the audience in your own system as the source of truth, or re-import before sending.

Next ​