# Transactional API

Send receipts, alerts, and product messages from your application.

## Endpoint

**POST** `/api/v1/emails`

Authenticate every request with an Email Bump API key. The sender must use your shared testing domain or a verified sending domain.

## Send an email

**Body parameters**

- `from` — *string*, required — Sender, optionally with a display name: `Acme <hi@acme.com>`. Must be your shared or a verified domain.
- `to` — *string*, required — Recipient email address. A one-element array (`["jamie@example.com"]`) is accepted too, for clients ported from an API that takes a list.
- `subject` — *string*, optional — Subject line (or inherited from a template).
- `html` — *string*, optional — HTML body.
- `text` — *string*, optional — Plain-text body.
- `reply_to` — *string*, optional — Reply-To address.
- `template` — *object*, optional — Render a saved template: `{ "id", "variables" }` instead of raw content.
- `template_id` — *string*, optional — Flat alternative to `template.id`.
- `variables` — *object*, optional — Flat alternative to `template.variables`. Values are readable both at the top level (`{{ order_id }}`) and under `contact`.
- `attachments` — *array*, optional — Files to send along. See [Attachments](#attachments).
- `scheduled_at` — *string*, optional — RFC 3339 future time to send later instead of now.
- `stream` — *string*, optional — `transactional` (default) or `marketing`. See [Marketing over the API](#marketing-over-the-api).

```bash
curl -X POST https://emailbump.com/api/v1/emails \
  -H "Authorization: Bearer ebk_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme <orders@acme.com>",
    "to": "jamie@example.com",
    "reply_to": "support@acme.com",
    "subject": "Your order has shipped",
    "html": "<h1>On the way</h1><p>Tracking: EB-2048</p>",
    "text": "Your order is on the way. Tracking: EB-2048"
  }'
```

Provide `html`, `text`, or both. When only HTML is supplied, Email Bump creates a text version. When only text is supplied, it creates a minimal HTML part.

**One recipient.** This endpoint takes a single `to` address — there's no `cc` or `bcc`. Each send is metered, tracked and unsubscribed on its own, so a list of five addresses is five calls, not one send with five recipients. Pass more than one and you'll get a `400` saying exactly that. When you need real `cc`/`bcc`, send the same message over [SMTP](https://emailbump.com/docs/smtp) with the same key: it accepts ordinary MIME and lands in the same log. To reach an audience in one request, use a [campaign](https://emailbump.com/docs/campaigns-api).

## Marketing over the API

Add `"stream": "marketing"` to send a promotional one-off without building a campaign or flow — an announcement to one person, a hand-triggered win-back, your own sending logic driving the API. The message then carries what marketing mail must: the recipient is checked against their contact record and **refused if they've unsubscribed** or their address is suppressed, the marketing footer with its unsubscribe link is appended, one-click unsubscribe headers are set, and the send is classified under the marketing stream in analytics.

```bash
curl -X POST https://emailbump.com/api/v1/emails \
  -H "Authorization: Bearer ebk_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme <hello@acme.com>",
    "to": "jamie@example.com",
    "subject": "Something new for you",
    "html": "<p>We just launched…</p>",
    "stream": "marketing"
  }'
```

A recipient who isn't a contact yet is created as one, the same way the [Events API](https://emailbump.com/docs/events-api) does — the unsubscribe link needs a record to point at. `scheduled_at` isn't supported on this stream yet; to schedule marketing email, use a [campaign](https://emailbump.com/docs/campaigns-api). Transactional sends (the default) are unchanged: no footer, no consent gate — receipts and password resets go to unsubscribed recipients too, as they should.

## What happened to it

**GET** `/api/v1/emails/{id}` — the id `POST /v1/emails` returned, and what became of the message:

```bash
curl https://emailbump.com/api/v1/emails/50b0b9a4-d447-480a-ad5b-3e1b999dd842 \
  -H "Authorization: Bearer ebk_your_key"
```

```json
{
  "object": "email",
  "id": "50b0b9a4-…",
  "to": "jamie@example.com",
  "subject": "Your order has shipped",
  "status": "sent",
  "error": null,
  "events": [
    { "type": "send",     "occurred_at": "2026-08-03T01:27:57Z" },
    { "type": "delivery", "occurred_at": "2026-08-03T01:27:59Z" },
    { "type": "open",     "occurred_at": "2026-08-03T01:31:12Z", "machine_open": false }
  ]
}
```

`status` is our own record of the submission; `events` is what the provider reported afterwards. The distinction matters: `"sent"` means it was accepted for delivery, not that it arrived. A message to a domain with no MX record is `"sent"` and then bounces — the `bounce` event is where that shows up, usually within seconds.

## Attachments

Send a receipt with its invoice, a report, a photo — anything, as base64. Up to 20 files totalling 25 MB per message.

| Parameter | Type | Notes |
|---|---|---|
| `filename` | string, required | The name the recipient sees when they save it. |
| `content` | string, required | The file, base64-encoded. A whole `data:` URI works too, so a browser's `FileReader` result can be passed straight through. |
| `content_type` | string | MIME type. Guessed from the filename when you leave it out. |
| `content_id` | string | Set this to embed the file in the HTML instead of listing it at the bottom, and reference it as `<img src="cid:the-id">`. |

```bash
curl -X POST https://emailbump.com/api/v1/emails \
  -H "Authorization: Bearer ebk_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme <billing@acme.com>",
    "to": "jamie@example.com",
    "subject": "Your invoice",
    "html": "<p>Thanks! Your invoice is attached.</p><img src=\"cid:logo\">",
    "attachments": [
      { "filename": "invoice-4821.pdf", "content": "JVBERi0xLjQK..." },
      { "filename": "logo.png", "content": "iVBORw0KGgo...", "content_id": "logo" }
    ]
  }'
```

A PDF invoice is normal and delivers fine. Executables, archives and macro-enabled documents are what spam filters look for — if a message must carry one, a link to it delivers far better than the file itself.

## Schedule for later

Add `scheduled_at` (RFC 3339, must be in the future) to send at a specific time. The response comes back with `status: "scheduled"`; Email Bump delivers it when it comes due.

```bash
curl -X POST https://emailbump.com/api/v1/emails \
  -H "Authorization: Bearer ebk_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme <orders@acme.com>",
    "to": "jamie@example.com",
    "subject": "Your trial ends soon",
    "html": "<p>Just a heads up…</p>",
    "scheduled_at": "2026-08-01T09:00:00Z"
  }'
```

## Use a template

Pass a saved template ID and variables instead of raw content. Variables are available at the top level and under `contact`.

```json
{
  "from": "Acme <orders@acme.com>",
  "to": "jamie@example.com",
  "template": {
    "id": "9d7073b0-63b0-4ad0-925e-c43f04619ec2",
    "variables": {
      "first_name": "Jamie",
      "order_id": "EB-2048"
    }
  }
}
```

## Response

```json
{
  "id": "6dde5322-c940-43e5-84cc-97a2d8c69c08",
  "message_id": "provider-message-id",
  "to": "jamie@example.com",
  "from": "Acme <orders@acme.com>",
  "subject": "Your order has shipped",
  "status": "sent",
  "created_at": "2026-07-20T18:42:10Z"
}
```

Every accepted send is recorded in **Developers → Transactional**, including its rendered content, original payload, and event timeline.
