Skip to main content
Sending is one endpoint, two modes:
  • New conversation — pass to, subject, and a body. We pick a mailbox from the pool, register the conversation, fire email.sent.
  • Reply to existing — pass convId and a body. We resolve recipient, subject, threading headers, and the bound mailbox from the conv.

New conversation

text and html can both be set; recipients see the multipart message their client prefers. Most production senders set both. If you’re stitching to a thread that started outside 12m (e.g. you imported a CRM history), pass inReplyTo and references:

Reply to a thread

When you have the convId from a prior send (or from a webhook), replying is one field:
We pull recipient, subject (with the Re: prefix), threading headers, and — critically — the same backing mailbox from the conversation state. Recipients see a coherent thread from one consistent sender. convId is mutually exclusive with to / subject. Cross-identity replies (a convId that lives on a different identity) return 404.

Idempotency

Every send accepts an optional Idempotency-Key header. Same key with the same body returns the original response (with Idempotent-Replayed: true); same key with a different body returns 409. Keys live for 24 hours.
For sequencers, the right key is <leadId>:<step> — if the worker crashes after we accepted the send but before your DB recorded it, the retry returns the same response and you don’t double-send.

Response

The fields worth knowing:
  • convId — store this against your lead/contact record. Every follow-up uses it.
  • affinitynew (we created an affinity row), existing (we reused the recipient’s pinned mailbox), or reassigned (the pinned mailbox was unhealthy or capped, we picked another).
  • account.email — the rotated mailbox we used. Surfaced for transparency and deliverability dashboards. You don’t need to act on it.
  • remaining — daily-cap budget left on this identity today.
Status code: 202 if anything was accepted, 429 if every recipient was rejected (typically cap_exceeded).

What we don’t support yet

In the interest of not lying to you:
  • Scheduled sends. No sendAt. Every send dispatches immediately. If you need “fire at 9am Tuesday in the prospect’s timezone,” run a scheduler in your own infra and call us at fire time. The Idempotency-Key makes the retry semantics safe.
  • Attachments. Not supported. If your use case needs them, tell us.
  • Custom headers. No way to set List-Unsubscribe / List-Unsubscribe-Post today. This is high-priority because Gmail/Yahoo bulk-sender requirements increasingly need it; we’ll ship it.
  • Multi-recipient sends. to accepts an array, but we send to each recipient separately so each gets its own conversation. Bulk broadcasts to cc lists aren’t a 12m use case — that’s transactional email territory.