Skip to content
Developer platform · Messages

Send email exactly once

Every send names an authorized mailbox and requires an idempotency key so network retries cannot create duplicate messages.

Queue a message

A 202 response means the message is durably queued. Track message.sent, message.delivered, message.bounced, or message.failed afterward.

                        const queued = await maildeck.messages.send(
  {
    fromMailboxId: 'mailbox_id',
    to: ['customer@example.com'],
    subject: 'Your receipt',
    text: 'Thanks for your order.',
  },
  { idempotencyKey: crypto.randomUUID() },
);
                      

Request contract

fromMailboxId must identify a mailbox owned by the credential tenant. to requires 1–50 valid addresses; cc and bcc accept up to 50 each. subject accepts up to 998 characters, text up to 1,000,000, html up to 2,000,000, and attachmentIds up to 20 UUIDs. Ownership and current plan capacity are checked before queueing.

Asynchronous delivery process

The API reserves idempotency for 24 hours, creates the outbound job, writes audit context, and returns { id, object: "message", status: "queued" }. An outbound worker builds MIME, stores the sent object, submits it through SES, and emits message.sent. SES delivery notifications later become message.delivered, message.bounced, or message.failed.

  • 202 queued is durable acceptance, not recipient delivery
  • The returned id correlates API, job, events, webhooks, and support logs
  • Poll canonical state only for reconciliation; use webhooks for low latency

Retry safely

Use 8–255 opaque characters and reuse the same Idempotency-Key only with the byte-equivalent logical request. A completed retry returns the original status and object ID with Idempotent-Replayed: true. A different body returns idempotency_conflict; a still-running first request returns idempotency_pending.

Add attachments before sending

Prepare with POST /v1/attachments/uploads, Idempotency-Key, mailboxId, filename, mediaType, sizeBytes and hexadecimal SHA-256. PUT bytes with the returned headers to the ten-minute URL, then POST /v1/attachments/uploads/{uploadId}/complete. Repeated completion returns the same attachment ID. GET /v1/attachments/{attachmentId}/download-url provides a sixty-second stream; each invocation rechecks current authorization and message visibility. Never forward an API key to storage or log signed URLs. Individual files and full MIME emails are limited to 25 MiB; MIME includes base64 overhead. Legacy JSON transfers remain for small files. No antivirus scanning is implemented

Update message state

PATCH /v1/messages/{messageId} requires messages:write plus mailboxId and at least one mutation: isRead, isStarred, or folder. Supported folders are inbox, sent, archive, spam, and trash. Successful mutations emit message.updated so Webmail, API consumers, and webhook receivers converge on the same state.

Next step

Create the first mailbox at no cost.

Start freeLog in