# Dbrij Send Documentation

Everything you need to implement email with Dbrij Send: domains, sending, templates, audiences, broadcasts, automations, suppressions, receiving, webhooks and limits.

Canonical page: https://dbrij.com/products/send/docs

## Introduction

**Dbrij Send** is the email platform for developers: transactional sending, templates, audiences, broadcasts, automations, suppressions, receiving and webhooks, all from your own domain, all through one API. Every message keeps a timeline of what happened to it, and these docs alone are enough to implement email end to end.

Base URL`https://api.dbrij.com/api`

Every endpoint is relative to that base. All requests and responses are JSON. Authenticate with an API key (see Authentication).

Every success response is wrapped in an envelope: `{ "success": true, "data": … }`. Read your payload from `data`, never from the top level; the Response examples throughout show the envelope. Errors are wrapped the same way with `success: false` (see Plans, errors and limits).

## Quickstart

Three steps from nothing to a delivered email.

**1 · Verify your domain.** In the Send dashboard, open Domains and add a domain you own. We issue the DKIM, SPF and MX records; publish them at your DNS host and verification completes on its own within minutes.

**2 · Create an API key.** On the API keys page, create a key with the `emails:send` scope. You choose the address it sends as when you create it, so the key IS the sender: `from` can be omitted on every call. Use a `test` key while building (nothing is relayed, nothing billed) and a `live` key in production.

**3 · Send.**

Your first email

```
curl -X POST https://api.dbrij.com/api/emails \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "to": "jane@example.com",
        "subject": "Hello from Dbrij Send",
        "text": "It works." }'
```

Then watch it move: `GET /emails/:id/events` shows the timeline (queued, sent, delivered, opened), and a webhook endpoint (see Webhooks) tells your app the same story as it happens.

## Authentication

Authenticate every request with an API key from the Send dashboard, sent as a Bearer token.

Authorization header

```
Authorization: Bearer dbrij_live_xxxxxxxxxxxxxxxxxxxxxxxx
```

A key created with the `emails:send` scope is **bound to one sender address**, chosen at creation from the addresses your account can send as. Every send through that key goes out as that address, and `from` can be omitted entirely, so a leaked key is one identity, never a whole domain.

No email ever goes out as a bare address. The display name is, in order: the one in your `from` string (`"Acme Billing <billing@acme.com>"`), the sender name on your key, or the domain said like a name: `hello@ricuit.com` arrives as `Ricuit <hello@ricuit.com>`, never as "hello".

**Test keys.** A key created in the test environment (`dbrij_test_…`) runs the whole pipeline (validation, suppression, tracking, events, webhooks) but delivery is simulated: nothing is relayed, nothing counts against your plan. The email comes back with `"sandbox": true` and status `delivered`.

SCOPES

|  |  |  |
| --- | --- | --- |
| emails:send | scope | Send, schedule and cancel emails. |
| emails:read | scope | Read emails, timelines, domains, stats and every list. |
| templates:write | scope | Create and edit templates. |
| audiences:write | scope | Manage audiences and contacts. |
| broadcasts:send | scope | Create, send and cancel broadcasts. |
| automations:write | scope | Create and manage automations. |
| suppressions:write | scope | Add and remove suppressions. |
| webhooks:manage | scope | Manage webhook endpoints and replay deliveries. |
| inbound:read | scope | Read received email. |

## Domains

Send delivers from **your own domain**: each one gets its own DKIM key on our delivery network, and DMARC aligns because the mail really is yours. Add and verify domains on the Send dashboard's Domains page; a domain can also be **granted** to you by the company that owns it (from company email settings), which authorizes your keys without giving you the domain.

Each domain carries three switches on the dashboard: **open tracking** (a pixel per email), **click tracking** (links route through Send and record clicks, 302 only to the stored URL), and **receiving** (mail to unassigned addresses lands in your workspace, see Receiving). Tracking is applied before the message is DKIM signed, so signatures always verify.

GET`/email-domains`emails:read

List sendable domains

The domains your account currently holds send access on (verified domains only).

Request

```
curl -X GET https://api.dbrij.com/api/email-domains \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": [ { "id": "d1a2…", "domain": "acme.com", "status": "active", "organizationId": "o3b4…" } ]
}
```

## Sending email

The transactional rail: one call, one email, a timeline and a webhook trail. Suppressed recipients are dropped before anything sends: an email whose every recipient is suppressed never relays and is never billed.

POST`/emails`emails:send

Send an email

Send one email (or schedule it with a future scheduledAt). Your API key is bound to the address it sends as, so from can be omitted entirely. Supports an Idempotency-Key header, see below.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| from | string | Optional: your key already knows its sender address. When given, it must match the key's address ("Name <addr@domain>" adds a display name; a bare address gets the domain's name, so hello@ricuit.com arrives as Ricuit). |
| to* | string \| string[] | Recipient address(es). Max 50 recipients total across to/cc/bcc. |
| cc | string[] | Carbon copy recipients. |
| bcc | string[] | Blind carbon copy recipients. |
| replyTo | string | Reply-To address. |
| subject | string | Subject line (single line, max 998 chars). Required unless a templateId supplies it. |
| html | string | HTML body. Provide html or text (or both), or a templateId. |
| text | string | Plain text body. |
| templateId | string | Render a saved template as the body; explicit subject/html/text win over it. |
| variables | object | Values for the template's {{merge tags}}. |
| headers | object | Up to 10 custom headers. Structural headers (From, To, Subject, Content-Type, …) are rejected. |
| attachments | Attachment[] | Up to 10, 10MB total decoded. Each: { filename, content? (base64), url? (https), contentType? }. See Attachments below. |
| scheduledAt | string (ISO-8601) | A future time schedules the send instead of sending now. |

Request

```
curl -X POST https://api.dbrij.com/api/emails \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "from": "Acme <no-reply@acme.com>",
  "to": ["jane@example.com"],
  "subject": "Welcome to Acme",
  "html": "<h1>Welcome!</h1><p>Glad to have you.</p>",
  "text": "Welcome! Glad to have you."
}'
```

Request body

```
{
  "from": "Acme <no-reply@acme.com>",
  "to": ["jane@example.com"],
  "subject": "Welcome to Acme",
  "html": "<h1>Welcome!</h1><p>Glad to have you.</p>",
  "text": "Welcome! Glad to have you."
}
```

Response 200

```
{
  "success": true,
  "data": {
    "id": "e5d3…",
    "from": "Acme <no-reply@acme.com>",
    "to": ["jane@example.com"],
    "cc": [],
    "bcc": [],
    "replyTo": null,
    "subject": "Welcome to Acme",
    "status": "sent",
    "scheduledAt": null,
    "sentAt": "2026-07-01T09:00:00.000Z",
    "deliveredAt": null,
    "messageId": "<a1b2…@acme.com>",
    "lastError": null,
    "sandbox": false,
    "createdAt": "2026-07-01T09:00:00.000Z"
  }
}
```

POST`/emails/batch`emails:send

Send a batch

Up to 100 sends in one call, each going out as your key's sender address (from may be omitted per item). Scheduling is not allowed in a batch. Results are per item. A failed item does not fail the batch. An Idempotency-Key covers the whole batch, per item under the hood.

Request

```
curl -X POST https://api.dbrij.com/api/emails/batch \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '[
  { "to": "jane@example.com", "subject": "Hi Jane", "text": "…" },
  { "to": "sam@example.com", "subject": "Hi Sam", "text": "…" }
]'
```

Request body

```
[
  { "to": "jane@example.com", "subject": "Hi Jane", "text": "…" },
  { "to": "sam@example.com", "subject": "Hi Sam", "text": "…" }
]
```

Response 200

```
{
  "success": true,
  "data": [
    { "id": "e5d3…", "status": "sent" },
    { "error": "Invalid to recipient: bob@" }
  ]
}
```

GET`/emails`emails:read

List emails

Your workspace's sent emails, newest first.

QUERY PARAMETERS

|  |  |  |
| --- | --- | --- |
| limit | number | Max emails (1 to 100, default 20). |
| before | string | Cursor: an ISO-8601 time or an email id. Returns emails created before it. |

Request

```
curl -X GET https://api.dbrij.com/api/emails \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": [ { "id": "e5d3…", "to": ["jane@example.com"], "subject": "Welcome to Acme", "status": "delivered", … } ]
}
```

GET`/emails/:id`emails:read

Get an email

Fetch one email, including its delivery status and any error.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The email id. |

Request

```
curl -X GET https://api.dbrij.com/api/emails/:id \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": { "id": "e5d3…", "status": "delivered", "deliveredAt": "2026-07-01T09:00:04.000Z", … }
}
```

DELETE`/emails/:id`emails:send

Cancel a scheduled email

Cancel a scheduled email before it fires. Only status "scheduled" can be cancelled.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The email id. |

Request

```
curl -X DELETE https://api.dbrij.com/api/emails/:id \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": { "id": "e5d3…", "status": "cancelled", … }
}
```

GET`/emails/:id/events`emails:read

Get an email's timeline

Every event the email produced, in order: queued, sent, delivered, opened, clicked, bounced, complained.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The email id. |

Request

```
curl -X GET https://api.dbrij.com/api/emails/:id/events \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": [ { "id": "ev1…", "type": "delivered", "recipient": "jane@example.com", "createdAt": "…" },
    { "id": "ev2…", "type": "opened", "createdAt": "…" } ]
}
```

### Idempotency

Pass an `Idempotency-Key` header (max 190 chars) on `POST /emails`. Retrying with the same key returns the original email instead of sending a duplicate. On `POST /emails/batch` one key (max 180 chars) covers the whole batch, per item under the hood: a retried batch skips what already landed and finishes what did not.

Idempotent send

```
curl -X POST https://api.dbrij.com/api/emails \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: welcome-user-42" \
  -d '{ "to": "jane@example.com",
        "subject": "Welcome to Acme", "text": "Hi Jane!" }'
```

### Scheduling

Set `scheduledAt` to a future ISO-8601 time and the email is queued instead of sent (status `scheduled`). Cancel it any time before it fires with `DELETE /emails/:id`. Scheduling is not available in `/emails/batch`, and send access is checked again at fire time: access revoked between scheduling and sending fails the send.

### Attachments

ATTACHMENT FIELDS

|  |  |  |
| --- | --- | --- |
| filename* | string | File name (max 255 chars). |
| content | string (base64) | Inline file body. Provide content or url. |
| url | string (https) | Public https URL fetched on our servers at send time. |
| contentType | string | MIME type, e.g. "application/pdf". |

Up to 10 attachments per email, 10MB total (decoded).

## Templates

A template is a versioned body with `{{variable}}` merge tags. One template serves all three rails: transactional sends (`templateId` + `variables` on `POST /emails`), broadcasts, and automation steps. Contact fields (`firstName`, `lastName`, `email`, custom fields) fill the tags automatically on broadcasts and automations.

POST`/templates`templates:write

Create a template

A reusable body with {{variable}} merge tags, detected automatically. Send with it by passing templateId + variables to POST /emails, or reference it from broadcasts and automation steps. Every save appends a version. Instead of html you can pass a design from the template library (layout, palette, typography, header and your copy, as the dashboard saves it): the HTML renders server side and the template stays editable as a form.

Request

```
curl -X POST https://api.dbrij.com/api/templates \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Welcome", "subject": "Hello {{firstName}}", "html": "<p>Hi {{firstName}}, welcome aboard.</p>" }
// or, from the library:
{ "name": "Weekly", "subject": "This week at Acme", "design": { "layout": "weekly", "palette": "ocean", "typography": "modern", "header": "band", "content": { "companyName": "Acme", "title": "…" } } }'
```

Request body

```
{ "name": "Welcome", "subject": "Hello {{firstName}}", "html": "<p>Hi {{firstName}}, welcome aboard.</p>" }
// or, from the library:
{ "name": "Weekly", "subject": "This week at Acme", "design": { "layout": "weekly", "palette": "ocean", "typography": "modern", "header": "band", "content": { "companyName": "Acme", "title": "…" } } }
```

Response 200

```
{
  "success": true,
  "data": { "id": "t1a2…", "name": "Welcome", "subject": "Hello {{firstName}}", "variables": ["firstName"], "currentVersion": 1, … }
}
```

GET`/templates`emails:read

List templates

Every template in the workspace, newest first.

Request

```
curl -X GET https://api.dbrij.com/api/templates \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": [ { "id": "t1a2…", "name": "Welcome", "variables": ["firstName"], "currentVersion": 3, … } ]
}
```

GET`/templates/:id`emails:read

Get a template

One template with its full body.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The template id. |

Request

```
curl -X GET https://api.dbrij.com/api/templates/:id \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": { "id": "t1a2…", "name": "Welcome", "html": "<p>…</p>", "text": null, … }
}
```

PATCH`/templates/:id`templates:write

Update a template

Save a new version. Emails already sent keep the version they rendered from.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The template id. |

Request

```
curl -X PATCH https://api.dbrij.com/api/templates/:id \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "subject": "Hello {{firstName}} 👋" }'
```

Request body

```
{ "subject": "Hello {{firstName}} 👋" }
```

Response 200

```
{
  "success": true,
  "data": { "id": "t1a2…", "currentVersion": 4, … }
}
```

DELETE`/templates/:id`templates:write

Delete a template

Removes the template. Automation steps that reference it stop sending.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The template id. |

Request

```
curl -X DELETE https://api.dbrij.com/api/templates/:id \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Returns `204` (no body).

## Audiences and contacts

An audience is a contact list. Broadcasts send to one; automations can trigger on someone joining one. A contact who unsubscribes is marked across every audience in the workspace and suppressed, so nothing sends to them again.

POST`/audiences`audiences:write

Create an audience

A contact list for broadcasts and automations.

Request

```
curl -X POST https://api.dbrij.com/api/audiences \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Newsletter" }'
```

Request body

```
{ "name": "Newsletter" }
```

Response 200

```
{
  "success": true,
  "data": { "id": "a1b2…", "name": "Newsletter", "contactCount": 0, … }
}
```

GET`/audiences`emails:read

List audiences

Every audience with its live contact count.

Request

```
curl -X GET https://api.dbrij.com/api/audiences \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": [ { "id": "a1b2…", "name": "Newsletter", "contactCount": 1240, … } ]
}
```

GET`/audiences/:id/contacts`emails:read

List contacts

The audience's contacts, newest first.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The audience id. |

QUERY PARAMETERS

|  |  |  |
| --- | --- | --- |
| limit | number | Max contacts (default 50). |
| before | string (ISO-8601) | Return contacts created before this time. |

Request

```
curl -X GET https://api.dbrij.com/api/audiences/:id/contacts \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": [ { "id": "c1a2…", "email": "jane@example.com", "firstName": "Jane", "unsubscribed": false, … } ]
}
```

POST`/audiences/:id/contacts`audiences:write

Add a contact

Adding a contact fires contact.created and can trigger a contact.added automation. The same email joins an audience once; adding again updates the fields.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The audience id. |

Request

```
curl -X POST https://api.dbrij.com/api/audiences/:id/contacts \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "email": "jane@example.com", "firstName": "Jane", "lastName": "Doe", "fields": { "plan": "pro" } }'
```

Request body

```
{ "email": "jane@example.com", "firstName": "Jane", "lastName": "Doe", "fields": { "plan": "pro" } }
```

Response 200

```
{
  "success": true,
  "data": { "id": "c1a2…", "email": "jane@example.com", "firstName": "Jane", … }
}
```

PATCH`/audiences/:id/contacts/:contactId`audiences:write

Update a contact

Change names, custom fields, or the unsubscribed flag.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The audience id. |
| contactId* | string (path) | The contact id. |

Request

```
curl -X PATCH https://api.dbrij.com/api/audiences/:id/contacts/:contactId \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "fields": { "plan": "scale" } }'
```

Request body

```
{ "fields": { "plan": "scale" } }
```

Response 200

```
{
  "success": true,
  "data": { "id": "c1a2…", "fields": { "plan": "scale" }, … }
}
```

POST`/audiences/:id/contacts/import`audiences:write

Import contacts

Bulk upsert up to 10,000 contacts in one call. Existing emails are updated, new ones created; the result counts both.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The audience id. |

Request

```
curl -X POST https://api.dbrij.com/api/audiences/:id/contacts/import \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "contacts": [ { "email": "jane@example.com", "firstName": "Jane" }, { "email": "sam@example.com" } ] }'
```

Request body

```
{ "contacts": [ { "email": "jane@example.com", "firstName": "Jane" }, { "email": "sam@example.com" } ] }
```

Response 200

```
{
  "success": true,
  "data": { "created": 1, "updated": 1, "skipped": 0 }
}
```

DELETE`/audiences/:id/contacts/:contactId`audiences:write

Remove a contact

Removes the contact from this audience (their suppression state, if any, is untouched).

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The audience id. |
| contactId* | string (path) | The contact id. |

Request

```
curl -X DELETE https://api.dbrij.com/api/audiences/:id/contacts/:contactId \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Returns `204` (no body).

## Broadcasts

One message to a whole audience, through the same pipeline as transactional mail, so every recipient gets a real email row with tracking and a timeline. The body freezes when sending starts; batches go out at a steady pace; and sending **halts itself** when bounce or complaint rates spike, because a burning list costs the domain more than the campaign is worth. Every message carries a working one click unsubscribe.

POST`/broadcasts`broadcasts:send

Create a broadcast

One message to a whole audience through the same pipeline as transactional mail: every recipient gets a real email row with its own timeline. Compose with a templateId or inline html.

Request

```
curl -X POST https://api.dbrij.com/api/broadcasts \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "August update", "audienceId": "a1b2…", "fromEmail": "news@acme.com", "subject": "What is new", "html": "<p>Hi {{firstName}},…</p>" }'
```

Request body

```
{ "name": "August update", "audienceId": "a1b2…", "fromEmail": "news@acme.com", "subject": "What is new", "html": "<p>Hi {{firstName}},…</p>" }
```

Response 200

```
{
  "success": true,
  "data": { "id": "b1c2…", "status": "draft", "totals": { "recipients": 0, … }, … }
}
```

POST`/broadcasts/:id/send`broadcasts:send

Send or schedule it

Freezes the body, snapshots the audience, and starts sending in paced batches (or schedules for a future scheduledAt). Suppressed and unsubscribed contacts are skipped; an unsubscribe link is added to every message; sending halts itself when bounces or complaints spike.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The broadcast id. |

Request

```
curl -X POST https://api.dbrij.com/api/broadcasts/:id/send \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "scheduledAt": "2026-08-20T09:00:00.000Z" }'
```

Request body

```
{ "scheduledAt": "2026-08-20T09:00:00.000Z" }
```

Response 200

```
{
  "success": true,
  "data": { "id": "b1c2…", "status": "scheduled", "scheduledAt": "2026-08-20T09:00:00.000Z", … }
}
```

GET`/broadcasts/:id`emails:read

Get a broadcast

Live totals while sending and after: recipients, sent, delivered, bounced, complained, opened, clicked, unsubscribed, skipped.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The broadcast id. |

Request

```
curl -X GET https://api.dbrij.com/api/broadcasts/:id \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": { "id": "b1c2…", "status": "sent", "totals": { "recipients": 1200, "sent": 1188, "opened": 480, … }, … }
}
```

POST`/broadcasts/:id/cancel`broadcasts:send

Cancel a broadcast

Stops a scheduled broadcast, or a sending one where it stands. Recipients already sent stay sent.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The broadcast id. |

Request

```
curl -X POST https://api.dbrij.com/api/broadcasts/:id/cancel \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": { "id": "b1c2…", "status": "canceled", … }
}
```

## Automations

A trigger and a straight line of steps. Someone joins an audience (or opens, clicks or receives an email), the automation waits a day, then sends the next template. Each address is enrolled **at most once per automation, ever** (triggering again never re enrolls), and a suppressed address's run is cancelled before it sends.

POST`/automations`automations:write

Create an automation

A trigger plus LINEAR wait/send steps (at most 20; waits between one minute and thirty days). Triggers: a contact joining an audience, or an email event (opened, clicked, delivered). Each address is enrolled at most once per automation, ever.

Request

```
curl -X POST https://api.dbrij.com/api/automations \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Welcome series", "trigger": { "type": "contact.added", "audienceId": "a1b2…" },
  "steps": [ { "type": "send", "templateId": "t1a2…", "fromEmail": "hello@acme.com" },
             { "type": "wait", "durationMs": 86400000 },
             { "type": "send", "templateId": "t9z8…", "fromEmail": "hello@acme.com" } ] }'
```

Request body

```
{ "name": "Welcome series", "trigger": { "type": "contact.added", "audienceId": "a1b2…" },
  "steps": [ { "type": "send", "templateId": "t1a2…", "fromEmail": "hello@acme.com" },
             { "type": "wait", "durationMs": 86400000 },
             { "type": "send", "templateId": "t9z8…", "fromEmail": "hello@acme.com" } ] }
```

Response 200

```
{
  "success": true,
  "data": { "id": "au1…", "status": "draft", "enrolledCount": 0, … }
}
```

POST`/automations/:id/enable`automations:write

Enable or pause

An automation only enrolls while active. Pausing holds running enrollments where they stand; enabling resumes them.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The automation id. |

Request

```
curl -X POST https://api.dbrij.com/api/automations/:id/enable \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": true }'
```

Request body

```
{ "enabled": true }
```

Response 200

```
{
  "success": true,
  "data": { "id": "au1…", "status": "active", … }
}
```

GET`/automations`emails:read

List automations

Every automation with its enrolled count. Also GET/PATCH/DELETE /automations/:id.

Request

```
curl -X GET https://api.dbrij.com/api/automations \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": [ { "id": "au1…", "name": "Welcome series", "status": "active", "enrolledCount": 214, … } ]
}
```

## Suppressions

The workspace's do not email list, checked before every send on every rail. Hard bounces, spam complaints and unsubscribes are added automatically; you can add and remove addresses yourself. Suppression wins before quota: a dropped recipient is never billed.

POST`/suppressions`suppressions:write

Suppress addresses

Add addresses to the do not email list every send checks. Hard bounces, spam complaints and unsubscribes land here automatically; this endpoint is for your own removals.

Request

```
curl -X POST https://api.dbrij.com/api/suppressions \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "emails": ["gone@example.com"], "note": "asked to be removed" }'
```

Request body

```
{ "emails": ["gone@example.com"], "note": "asked to be removed" }
```

Response 200

```
{
  "success": true,
  "data": { "added": 1 }
}
```

GET`/suppressions`emails:read

List suppressions

The workspace suppression list with each address's reason: unsubscribed, bounced, complained or manual.

QUERY PARAMETERS

|  |  |  |
| --- | --- | --- |
| limit | number | Max rows (default 50). |
| before | string (ISO-8601) | Return rows created before this time. |

Request

```
curl -X GET https://api.dbrij.com/api/suppressions \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": [ { "email": "gone@example.com", "reason": "manual", "note": "asked to be removed", "createdAt": "…" } ]
}
```

DELETE`/suppressions/:email`suppressions:write

Unsuppress an address

Remove one address from the list. Think twice about unsuppressing a bounce or a complaint: the next one costs reputation.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| email* | string (path) | The address to remove. |

Request

```
curl -X DELETE https://api.dbrij.com/api/suppressions/:email \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Returns `204` (no body).

## Receiving

Turn **receiving** on for a domain (Domains page) and mail sent to any address on it that has no company mailbox lands in your workspace: stored, listed here, and echoed to your app as an `email.received` webhook. Company mailboxes always resolve first, so receiving can never shadow a real inbox.

GET`/inbound`inbound:read

List received email

Email received on a domain with receiving turned on, newest first. Each arrival also fires an email.received webhook.

QUERY PARAMETERS

|  |  |  |
| --- | --- | --- |
| limit | number | Max rows (default 50). |
| before | string (ISO-8601) | Return rows created before this time. |

Request

```
curl -X GET https://api.dbrij.com/api/inbound \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": [ { "id": "in1…", "fromAddress": "customer@example.com", "to": ["anything@acme.com"], "subject": "Hi", … } ]
}
```

GET`/inbound/:id`inbound:read

Get a received email

The full message: html and text bodies, headers, authentication results and attachments (base64).

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| id* | string (path) | The inbound email id. |

Request

```
curl -X GET https://api.dbrij.com/api/inbound/:id \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": { "id": "in1…", "fromAddress": "customer@example.com", "html": "<p>…</p>", "attachments": [ { "filename": "invoice.pdf", … } ], … }
}
```

## Stats

The numbers behind the dashboard Metrics page: totals and rates over a window, plus a per day series for charts.

GET`/stats`emails:read

Sending stats

Aggregate counts and rates over your workspace, the dashboard Metrics page over HTTP.

QUERY PARAMETERS

|  |  |  |
| --- | --- | --- |
| days | number | The window, 1 to 90 (default 30). |

Request

```
curl -X GET https://api.dbrij.com/api/stats \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": { "from": "2026-07-15", "to": "2026-08-14", "totals": { "sent": 12480, "delivered": 12333, … }, "deliveryRate": 0.988, "openRate": 0.41, …, "series": [ … ] }
}
```

## Webhooks

Send POSTs a signed JSON envelope to your endpoints for each event. Register as many endpoints as your plan allows, each with its own secret and event filter. Deliveries are retried three times, logged, and replayable; an endpoint failing 20 deliveries in a row is paused (and the owner emailed) rather than silently dropped forever.

Envelope

```
{
  "id": "evt_…",
  "type": "email.delivered",
  "createdAt": "2026-07-01T09:00:04.000Z",
  "data": { … }
}
```

POST`/webhooks`webhooks:manage

Add a webhook endpoint

Send supports MANY endpoints, each with its own whsec_ secret (returned once) and an event filter (empty = every event). After 20 consecutive delivery failures an endpoint is paused and the owner emailed; fix it and enable it again.

Request

```
curl -X POST https://api.dbrij.com/api/webhooks \
  -H "Authorization: Bearer $DBRIJ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://api.yourapp.com/webhooks/dbrij", "events": ["email.delivered", "email.bounced"] }'
```

Request body

```
{ "url": "https://api.yourapp.com/webhooks/dbrij", "events": ["email.delivered", "email.bounced"] }
```

Response 200

```
{
  "success": true,
  "data": { "id": "w1a2…", "url": "https://…", "secret": "whsec_…", "events": ["email.delivered", "email.bounced"], … }
}
```

GET`/webhooks`emails:read

List endpoints

Every endpoint with its status and failure streak. Also PATCH/DELETE /webhooks/:id and POST /webhooks/:id/rotate-secret.

Request

```
curl -X GET https://api.dbrij.com/api/webhooks \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": [ { "id": "w1a2…", "url": "https://…", "status": "enabled", "consecutiveFailures": 0, … } ]
}
```

GET`/webhooks/deliveries`emails:read

The delivery log

Every attempted delivery: status code, attempts, duration and error. Filter with ?endpointId=.

Request

```
curl -X GET https://api.dbrij.com/api/webhooks/deliveries \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": [ { "id": "d1…", "event": "email.delivered", "statusCode": 200, "ok": true, "attempts": 1, "durationMs": 84, … } ]
}
```

POST`/webhooks/deliveries/:deliveryId/replay`webhooks:manage

Replay a delivery

Queues the exact logged payload to its endpoint again, signed fresh at delivery time.

PATH / BODY PARAMETERS

|  |  |  |
| --- | --- | --- |
| deliveryId* | string (path) | The delivery id from the log. |

Request

```
curl -X POST https://api.dbrij.com/api/webhooks/deliveries/:deliveryId/replay \
  -H "Authorization: Bearer $DBRIJ_API_KEY"
```

Response 200

```
{
  "success": true,
  "data": { "queued": true }
}
```

### Events

`email.sent`An email was handed to the mail transport.

data

```
{ "emailId": "e5d3…", "from": "Acme <no-reply@acme.com>", "to": ["jane@example.com"], "subject": "Welcome to Acme" }
```

`email.delivered`A recipient's mail server accepted the email.

data

```
{ "emailId": "e5d3…", "to": ["jane@example.com"], "subject": "…", "recipient": "jane@example.com" }
```

`email.failed`A send or a delivery failed (carries the reason).

data

```
{ "emailId": "e5d3…", "to": ["jane@example.com"], "subject": "…", "reason": "550 mailbox unavailable", "recipient": "jane@example.com" }
```

`email.bounced`A recipient hard bounced (the address is auto suppressed).

data

```
{ "emailId": "e5d3…", "to": ["jane@example.com"], "subject": "…", "reason": "550 mailbox unavailable", "recipient": "jane@example.com" }
```

`email.complained`A recipient marked the email as spam (auto suppressed).

data

```
{ "emailId": "e5d3…", "to": ["jane@example.com"], "subject": "…" }
```

`email.opened`A tracked email was opened (open tracking on).

data

```
{ "emailId": "e5d3…", "to": ["jane@example.com"], "subject": "…" }
```

`email.clicked`A tracked link was clicked (click tracking on).

data

```
{ "emailId": "e5d3…", "to": ["jane@example.com"], "subject": "…", "url": "https://acme.com/x" }
```

`email.received`Inbound mail arrived on a receiving enabled domain.

data

```
{ "inboundId": "in1…", "from": "customer@example.com", "to": ["anything@acme.com"], "subject": "Hi" }
```

`contact.created`A contact joined one of your audiences.

data

```
{ "contactId": "c1a2…", "audienceId": "a1b2…", "email": "jane@example.com", "unsubscribed": false }
```

`contact.unsubscribed`A contact unsubscribed (also suppressed workspace wide).

data

```
{ "email": "jane@example.com" }
```

`broadcast.sent`A broadcast finished sending, with its totals.

data

```
{ "broadcastId": "b1c2…", "name": "August update", "totals": { "recipients": 1200, "sent": 1188, … } }
```

`broadcast.halted`A broadcast stopped itself (bounce or complaint rate, or a plan limit).

data

```
{ "broadcastId": "b1c2…", "reason": "Stopped at 6% bounces…" }
```

### Verify the signature

Each delivery carries `X-Dbrij-Signature: sha256=…`, an HMAC of the raw request body using the endpoint's `whsec_` secret, and `X-Dbrij-Signature-V2: t=…,sha256=…`, an HMAC of `` `${t}.${body}` `` for replay protection. Verify one of them before trusting a payload.

Node

```
import { createHmac } from 'crypto';

function verify(rawBody, header, secret) {
  const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
  return header === expected; // compare to X-Dbrij-Signature
}

function verifyV2(rawBody, header, secret, toleranceSec = 300) {
  const [tPart, sigPart] = header.split(',');            // "t=1690000000,sha256=…"
  const t = tPart.slice(2);
  if (Math.abs(Date.now() / 1000 - Number(t)) > toleranceSec) return false;
  const expected = 'sha256=' + createHmac('sha256', secret).update(t + '.' + rawBody).digest('hex');
  return sigPart === expected;
}
```

## Plans, errors and limits

Send bills by monthly email volume, never per seat. Every account starts free; paid plans bill overage per extra thousand at renewal, from the closed month's usage. The free plan is a hard stop instead of a bill.

| Plan | Monthly | Emails / month | Overage / 1,000 | Contacts | Domains | Requests / min | Webhook endpoints | Retention |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Free | Free | 3,000 (100 a day) | Hard stop | 1,000 | 1 | 60 | 0 | 3 days |
| Launch | ₦12,000 / mo | 15,000 | ₦1,800 | 5,000 | 3 | 300 | 5 | 14 days |
| Starter | ₦40,000 / mo | 100,000 | ₦1,500 | 25,000 | 10 | 600 | 10 | 30 days |
| Growth | ₦90,000 / mo | 200,000 | ₦1,300 | 100,000 | 50 | 1,200 | 25 | 60 days |
| Scale | ₦270,000 / mo | 1,000,000 | ₦1,000 | 500,000 | 200 | 3,000 | 100 | 90 days |

### Rate limits

Every request counts toward your plan's per minute ceiling. Each response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`; a `429` adds `Retry-After` in seconds. New paid workspaces also ramp daily volume for the first two weeks, because deliverability lost is months to win back.

### Errors

|  |  |  |
| --- | --- | --- |
| 401 | Unauthorized | Missing, invalid, revoked or expired API key. |
| 403 | Forbidden | Key missing a required scope, or sending as an address it is not bound to. |
| 404 | Not Found | Unknown email, template, audience, broadcast or automation. |
| 402 | Payment Required | Monthly volume exhausted on the free plan. Upgrade in the dashboard. |
| 429 | Too Many Requests | Per minute rate ceiling, or the free plan's daily cap. |
| 422 / 400 | Validation | Malformed body or invalid field. |

Error shape

```
{
  "success": false,
  "statusCode": 403,
  "message": "This key sends as billing@acme.com. Use that from address, or omit from entirely.",
  "error": "Forbidden",
  "path": "/api/v1/emails",
  "timestamp": "2026-08-14T22:56:17.969Z"
}
```
