Docs / API

Available in v0.1

API endpoints

One outbound endpoint and a no-auth liveness probe. The contract below is the wire shape the SDK backend enforces — the SDK re-shapes your typed payload into this JSON before posting.

Endpoints

MethodPathAuthNotes
POST/api/v1/messagesBearer SDK keySend a message. Validates the payload, enforces from on the bound domain, forwards to the per-account receive-worker’s POST /api/email/send. Returns { id, status: "queued" } on success (201).
GET/healthnoneLiveness probe. Returns { ok: true }. Useful for uptime monitors and the SDK’s connectivity smoke.

Anything outside /api/v1/* and /health returns plain-text Not Found with status 404. The catch-all is registered at apps/sdk-backend/src/index.ts.

Request body

The JSON below is what the SDK backend receives. The SDK re-shapes your typed SendOptions into this exact shape, so you shouldn’t need to read or build this JSON directly.

POST /api/v1/messagesjson
{
  "from": "support@yourdomain.com",
  "fromName": "Support",                       // optional — display name
  "to": ["customer@example.com"],               // string or string[]
  "replyTo": "noreply@yourdomain.com",          // optional
  "subject": "Order #1234 confirmed",
  "text": "Plain text body",                    // optional but at least one
  "html": "<p>HTML body</p>",                   // optional but at least one
  "attachments": [                              // optional
    {
      "filename": "invoice.pdf",
      "content": "JVBERi0xLjQKJ...",            // base64 OR raw string
      "mimeType": "application/pdf"
    }
  ]
}

Constraints enforced by the handler before the request ever leaves the SDK backend:

  • from must be a valid RFC 5322 address and must land on the domain bound to the API key. A mismatch returns from_domain_mismatch (422).
  • Every to recipient must be a valid email address.
  • subject must be 1..998 characters.
  • At least one of text or html must be a non-empty string. Each body is capped at 1,000,000 characters.
  • The local-part of from must be 1..64 characters. The receive-worker does the route-table validation downstream.

Response

On success the SDK backend returns the queued message id with HTTP 201. The id is the numeric email.id row id from the receive-worker, stringified for JSON safety; status is the literal "queued" for v0.1.

201 Createdhttp
HTTP/1.1 201 Created
Content-Type: application/json

{
  "id": "msg_8421",
  "status": "queued"
}

The queued → sent → bounced lifecycle isn’t surfaced through the SDK in v0.1 — read it from the dashboard inbox, or wait for the v0.2 webhook delivery. The complete error envelope is documented on the Errors page.

Field renames between the SDK and the wire

The SDK keeps html as the body field name (it’s what email-library users expect), and the SDK backend re-shapes it to content when it forwards to the receive-worker’s /api/email/send. The attachments binary encoding and the single-recipient wrap happen entirely inside the SDK before the request leaves.

HopFieldTransform
SDK input → SDK → SDK backendhtmlPass-through. The SDK keeps html as the body field name; the SDK backend receives it as-is on the inbound payload.
SDK backend → receive-workercontentRenamed server-side in apps/sdk-backend/src/api/messages.ts: content: body.html. The receive-worker’s wire DTO uses content exclusively.
SDK input attachment.content (Uint8Array)attachment.content (string)Base64-encoded in the SDK via bytesToBase64 (uses btoa, no Buffer polyfill). Pass-through on every subsequent hop.
SDK input options.to (string)to (string[])Single recipient wrapped to [to]. Arrays pass through.

What the SDK does for you

  • JSON encoding. The SDK JSON.stringifys the body and sets Content-Type for you.
  • 15-second timeout. AbortSignal.timeout(15_000) matches the SDK backend’s upstream timeout, so you get UpstreamError rather than a generic 502 if the request stalls.
  • Auto-retries. The SDK retries 5xx, 408, and 429 with exponential backoff (capped at 4 s) up to maxRetries (default 2). 4xx is never retried — see Errors.
  • Request id. If you set requestId on the constructor, every outbound request carries X-Flowmails-Request-Id for cross-stack tracing.

Next up