Skip to main content
The Bulk API lets you incrementally build a notification job for a large number of users and then execute it with a single API call. While the Send API can deliver messages to multiple recipients, the Bulk API gives you more control over ingestion and lets you track progress as the job runs. Like sending a single message, the Bulk API works across channels (email, SMS, chat, in-app inbox, push) and providers (Twilio, SendGrid, etc).

Prerequisites

  • A Courier account with an API key
  • A published notification template (or inline content)
  • Recipient user IDs or profile data for the users you want to notify

Sending a Bulk Job

1

Create a bulk job

Define the job with a template and any global data that applies to all recipients. The response returns a jobId you’ll use for the remaining steps.
curl -X POST https://api.courier.com/bulk \
  -H "Authorization: Bearer YOUR_AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "message": {
      "message": {
        "template": "YOUR_TEMPLATE_ID"
      },
      "data": {
        "company_name": "Acme Corp"
      }
    }
  }'
Response:
{
  "jobId": "1-61e9dd53-b5bb6c863b7ffbe83ad4b28d"
}
All bulk endpoints support idempotent requests using the Idempotency-Key header. Include one if you want safe retries.
You can also pass optional fields like brand, locale, and a global data object that applies to every recipient. Per-user data (added in the next step) takes precedence over global data.
2

Ingest users into the job

Add recipients to the job using the jobId. You can call this endpoint multiple times to ingest users in batches; the job won’t expire until you run it.
curl -X POST https://api.courier.com/bulk/JOB_ID \
  -H "Authorization: Bearer YOUR_AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "users": [
      {
        "to": {
          "user_id": "spike_spiegel",
          "data": {
            "recipientName": "Spike Spiegel"
          }
        }
      },
      {
        "to": {
          "user_id": "faye_valentine",
          "data": {
            "recipientName": "Faye Valentine"
          }
        }
      }
    ]
  }'
Each user object supports these optional fields:
FieldDescription
to.user_idCourier user ID for the recipient
to.dataPer-user data that overrides the global data from the job
profileInline profile data (email, phone, etc.)
preferencesPer-user preference overrides
You can call this endpoint as many times as needed before running the job. The bulk job will not expire until it is invoked.
If there are errors (e.g. duplicate users), the response includes them alongside the successful count:
{
  "errors": [
    {
      "error": "Duplicate user",
      "user": {
        "profile": { "email": "foo@bar.com" },
        "data": { "recipientName": "Foo Bar" },
        "recipient": "foo-bar"
      }
    }
  ],
  "total": 1
}
3

Run the job

Once all users are ingested, execute the job. This triggers Courier to fan out and send individual messages to each recipient.
curl -X POST https://api.courier.com/bulk/JOB_ID/run \
  -H "Authorization: Bearer YOUR_AUTH_TOKEN"
Returns 202 Accepted. The job begins processing asynchronously.
A job can only be triggered once. If you need to send to more users after running, create a new job.
4

Monitor job status

Track the job’s progress using the jobId. The status response includes delivery statistics.
curl https://api.courier.com/bulk/JOB_ID \
  -H "Authorization: Bearer YOUR_AUTH_TOKEN"
{
  "job": {
    "definition": {
      "event": "V7E6M48EFQMB78H746QCCD1KSRAA"
    },
    "enqueued": 2,
    "failures": 0,
    "received": 2,
    "status": "COMPLETED"
  }
}
FieldDescription
receivedTotal users ingested into the job
enqueuedMessages placed on the pipeline
failuresErrors during job processing
statusCREATED, PROCESSING, or COMPLETED
You can also fetch individual user statuses with pagination:
curl "https://api.courier.com/bulk/JOB_ID/users?cursor=CURSOR" \
  -H "Authorization: Bearer YOUR_AUTH_TOKEN"
{
  "items": [
    {
      "recipient": "spike_spiegel",
      "messageId": "1-61e9dd7d-13c08339357603322c433d55",
      "status": "ENQUEUED"
    },
    {
      "recipient": "faye_valentine",
      "status": "PENDING"
    }
  ],
  "paging": {
    "cursor": "next_cursor",
    "more": true
  }
}
Each item’s messageId is the same ID you’d get from a regular /send request. You can look up individual messages in the Message Logs or via the Messages API. If you have Outbound Webhooks configured, Courier will invoke them throughout the message lifecycle.

What’s Next