Sending mail

Queue a message through the outbox, follow its delivery, schedule it, and undo or cancel it.

Send a message from a connected account with one outbox request. This page covers single sends. For other jobs, see:

  • Drafts: compose in the user's mailbox and send later.
  • Mail merge: one personalized message per recipient in one call.
  • Templates: stored, reusable content.
  • Tracking: open and click tracking, unsubscribe links.
  • Suppression lists: who bulk mail must skip.

Send a message

A send queues an outbox entry for the account and returns 201 at once. The account's provider (Gmail, Microsoft 365 or its SMTP server) sends it in the background.

The key needs the send scope. An account token for that account also works.

curl -X POST "https://api.workwae.com/v1/accounts/$ACCOUNT_ID/outbox" \
  -H "Authorization: Bearer $WORKWAE_API_KEY" \
  -H "content-type: application/json" \
  -d '{
  "to": [{ "name": "Ada Lovelace", "address": "ada@example.com" }],
  "subject": "Your invoice",
  "text": "Hi Ada, your invoice is attached.",
  "html": "<p>Hi Ada, your invoice is attached.</p>",
  "reference": "invoice-1042"
}'

Request fields

FieldMeaning
to, cc, bcc, replyToArrays of { name?, address }, up to 500 each. At least one of to, cc or bcc is required (no_recipients otherwise).
subject, text, htmlThe content. With format: "markdown", html holds Markdown and is converted to HTML.
attachmentsUp to 50 files; see Attachments.
fromOne of the account's send-as identities (GET /v1/accounts/{accountId}/identities). Omitted, it sends as the account itself.
signatureAppends the sender identity's signature. Omitted, it follows the account's signByDefault mail setting.
inReplyToMessageIdThe Workwae id of the message being answered. Sets In-Reply-To and References so the reply stays in its thread.
priorityhigh, normal (default) or low, written as the provider's priority headers.
referenceYour own identifier, echoed in the send.* events.
sendAtSchedule the send; see Scheduling and undo.
template, renderRender stored or inline content; see Templates.
trackingOpen and click tracking; see Tracking.
listIdA suppression list for bulk mail; see Tracking.

An unknown field is refused, not ignored. Every refusal and its reason is listed under Send a message in the API reference.

Attachments

attachments takes up to 50 objects of { filename, contentType, content }, with content in base64. To show one inline, set its contentId and reference it from the HTML as cid:....

await workwae.outbox.create({
  accountId,
  to: [{ address: "ada@example.com" }],
  subject: "Your invoice",
  html: '<p>Invoice attached.</p><img src="cid:logo">',
  attachments: [
    { filename: "invoice.pdf", contentType: "application/pdf", content: pdfBase64 },
    { filename: "logo.png", contentType: "image/png", content: logoBase64, contentId: "logo" },
  ],
});

Delivery and events

The entry's status moves from queued to sending, then ends sent, failed or cancelled. Follow it with webhooks or the change feed. Each event carries the entry's id as outboxId and your reference.

EventWhen
send.sentThe provider accepted the message.
send.retryingAn attempt failed; the entry will be tried again.
send.failedThe entry failed for good.
send.rescheduledsendAt was moved.
message.bouncedA bounce for the sent message arrived later.
  • Delivery is at least once: a retry first looks for a copy that already went out.
  • send.failed with code outcome_unknown means the server may have accepted the message but its reply was lost. Workwae does not send it again; decide yourself whether to resend.
  • GET /v1/accounts/{accountId}/outbox lists entries newest first (filter with status). GET .../outbox/{outboxId} reads one.
  • The sent copy indexed from the account's Sent folder carries the same outboxId.

Full payloads are in the mail events catalog.

Scheduling and undo

  • sendAt schedules a send. Omitted or in the past, it goes as soon as possible.
  • The account's undoSendSeconds setting (0 to 60) holds every send at least that long.
  • While an entry waits, undoUntil says until when cancelling undoes it.
# Move a queued send
curl -X PATCH "https://api.workwae.com/v1/accounts/$ACCOUNT_ID/outbox/$OUTBOX_ID" \
  -H "Authorization: Bearer $WORKWAE_API_KEY" \
  -H "content-type: application/json" \
  -d '{ "sendAt": "2026-11-02T09:00:00Z" }'

# Cancel (undo) it
curl -X DELETE "https://api.workwae.com/v1/accounts/$ACCOUNT_ID/outbox/$OUTBOX_ID" \
  -H "Authorization: Bearer $WORKWAE_API_KEY"

Once it is sending, it is too late

Cancelling or rescheduling an entry that is no longer queued returns 409 send_not_cancellable or send_not_reschedulable. The message says which state it is in.

Send limits

  • The tenant's plan caps the sends it may queue per day (UTC). Past the cap, a send returns 403 plan_limit_sends and a mail merge entry fails with that reason. GET /v1/usage shows the cap (sendsPerDay) and sendsToday.
  • A tenant whose payment is past due beyond the grace period is refused new sends with 402 billing_past_due. Sends already queued still go out.

Details are in Daily sends; request rate limits and other plan limits are in Rate limits.

On this page