Sending

Send from any inbox, reply inside a thread, and follow each message from queued to delivered. Retries are safe with an Idempotency-Key.

Send a message

POST/v1/inboxes/{id}/messages

Sends a new message from the inbox and starts a new thread. The response is 201 with the message object, its direction set to out.

FieldTypeDescription
to, cc, bccstring | string[]One address or a list. Plain addresses only, no display names. At least one recipient, at most 50 across all three.
reply_tostring | string[]Sets the Reply-To header.
subjectstringOne line, at most 998 characters. Defaults to empty.
text, htmlstringThe body. At least one of them is required.
headersobjectExtra headers such as {"X-Campaign": "oct"}. Mailbay sets From, To, Subject, Message-ID, threading and MIME headers itself, so those and any X-Mailbay-* are refused.
attachmentsAttachment[]filename, content as standard base64, and an optional content_type (defaults to application/octet-stream).

Addresses are lowercased and deduplicated; an address in to is dropped from cc and bcc. Bcc recipients get the message but never appear in its headers. The whole message, attachments included, may be at most 25 MB after encoding.

curl https://api.mailbay.dev/v1/inboxes/ibx_01jbz8w4k2q9/messages \
  -H "Authorization: Bearer $MAILBAY_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "bob@example.com",
    "subject": "Your report",
    "text": "Hi Bob, the report is attached.",
    "attachments": [
      { "filename": "report.pdf", "content_type": "application/pdf", "content": "JVBERi0xLjcK..." }
    ]
  }'

The free sandbox

Until your workspace has a verified domain, inboxes on your mailbay.email domain send in a sandbox, as Resend does: they can email the people in your workspace (the addresses they sign in with), other addresses under your own code, and the test addresses below. Any other recipient fails with 403 sandbox_recipient_not_allowed, and nothing is sent. The same rule applies to replies and to SMTP from a mail app. Verify a domain with mailbay domain add yourdomain.com and you can email anyone. GET /v1/me returns sandbox: true while the rule applies.

Test addresses

Send to these from any inbox or project to see each outcome without real delivery. Mailbay simulates the result, updates the status and emits the event, and never adds them to your suppressions. A +tag works too, such as delivered+signup@test.mailbay.dev.

AddressEventResult
delivered@test.mailbay.devmessage.deliveredAccepted by the recipient's server.
bounced@test.mailbay.devmessage.bouncedA permanent 550 5.1.1 bounce.
complained@test.mailbay.devmessage.complainedDelivered, then reported as spam.

Idempotency

Send an Idempotency-Key header (1 to 255 printable ASCII characters, a UUID works) on send and reply, and retrying after a timeout never sends twice. Keys are remembered per org for 24 hours.

RequestStatusResult
Same key, same body201Returns the message stored by the first request. Nothing is sent again.
Same key, different body409idempotency_conflict. Use a new key for a different request.
Same key, first still running409idempotency_in_progress. Retry in a moment for its result.
First request failedThe key is released, so a retry with the same key runs again.

Reply in a thread

POST/v1/messages/{id}/reply

Replies to a message in the same thread. Mailbay sets In-Reply-To, References and a Re: subject, so the reply threads in Gmail and Outlook. A reply to received mail goes to its Reply-To if it had one, otherwise to its sender. A reply to one of your own sent messages follows up with its recipients.

FieldTypeDescription
text, htmlstringThe body. At least one is required.
reply_allbooleanAlso copy everyone else on the original message. Defaults to false.
headers, attachmentsSame as sending.
curl https://api.mailbay.dev/v1/messages/msg_01jbz9c3/reply \
  -H "Authorization: Bearer $MAILBAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Thanks, on it", "reply_all": true}'

Message statuses

A sent message moves forward through these statuses. Each change also emits an event, delivered to your webhooks.

StatusEventMeaning
queuedmessage.queuedStored and handed to the mail server.
sentThe mail server accepted it for every recipient.
deliveredmessage.deliveredEvery recipient's server accepted it.
deferredmessage.deferredA temporary failure; the mail server keeps retrying.
bouncedmessage.bouncedA recipient's server refused it permanently.
failedmessage.failedThe mail server did not accept it, or gave up retrying.
complainedmessage.complainedA recipient reported it as spam.

With several recipients the message status summarizes them: complained wins, then deferred or sent while any recipient is still in flight, then bounced, failed and finally delivered.

Suppressions

GET/v1/suppressions

DELETE/v1/suppressions/{address}

A hard bounce (the address does not exist) or a spam complaint adds the address to your org's suppression list, and sending to it fails with recipient_suppressed. Soft bounces and policy blocks do not suppress. The list returns address, reason (hard_bounce, complaint or manual) and created_at, newest first. Delete an entry only when you know the address works again. Both endpoints need an org scoped key.

curl -X DELETE https://api.mailbay.dev/v1/suppressions/bob@example.com \
  -H "Authorization: Bearer $MAILBAY_API_KEY"
# 204 No Content

Plan limits

Every plan includes a number of sent recipients per calendar month (UTC); one message to three people counts as three. Received mail never counts. When the monthly limit is reached, sends fail with monthly_send_limit_reached until the first of next month. New orgs also send to at most 100 recipients a day for their first 7 days. See Pricing for each plan's numbers, and GET /v1/me for what you have used.

Automatic pause

Mailbay sends from shared IPs, so it pauses a workspace whose mail hurts their reputation. Over the last 24 hours, once the workspace has sent to at least 50 recipients, sending pauses when hard bounces reach 5% of those recipients and number at least 3, or spam complaints reach 0.1% and number at least 3. A single complaint never pauses sending. Every recipient of every message counts, from the API, replies and SMTP alike; test addresses never count. The check runs each time a hard bounce or a complaint comes in.

While paused, every send and reply fails with sending_paused (403, SMTP 554 5.7.1), forwarding skips new mail, and the error message gives the reason, such as the hard bounce rate reached 6% (3 hard bounces among 50 recipients) in the last 24 hours. Receiving mail keeps working. The workspace owner gets one email, the dashboard shows a banner, the audit log records workspace.sending_paused, and webhooks get a sending.paused event. GET /v1/me returns the reason in org.paused_reason.

Only Mailbay support can resume sending; workspace owners cannot. Remove the addresses that bounced or complained (they are already suppressed), check where the rest of the list came from, then write to support@mailbay.dev with what changed. After a resume the rates are measured again from that moment.

Errors

CodeStatusMeaning
sandbox_recipient_not_allowed403The workspace has no verified domain, so mailbay.email addresses may only email its members and the test addresses. Verify a domain with mailbay domain add to email anyone.
catch_all_cannot_send403The Catch-all only receives. Create an inbox with the address you want to send from.
recipient_suppressed400A recipient hard bounced or complained before. Remove it, or delete the suppression if you are sure the address works again.
sending_paused403Sending is paused for this workspace because too many recipients hard bounced or complained. The message says why and since when. See Automatic pause.
monthly_send_limit_reached402The plan's monthly recipients are used up. Upgrade, or wait for the first of next month (UTC). See Pricing.
daily_send_limit_reached402Orgs younger than 7 days send to at most 100 recipients a day (UTC). The limit lifts on day 8.
transport_unavailable403The server has no mail transport configured. Self hosted operators set KUMO_INJECT_URL.
idempotency_conflict409The Idempotency-Key was already used with a different request body.
idempotency_in_progress409A request with the same key is still running. Retry in a moment.
too_many_recipients400More than 50 addresses across to, cc and bcc (none at all is missing_recipients).
missing_body400Neither text nor html was given.
invalid_address400An address is not a plain address such as user@example.com.
reserved_header400A header in headers is one Mailbay sets itself. Use the matching field.
invalid_attachment400An attachment has no filename, a bad content type or content that is not base64.
message_too_large400The encoded message is over 25 MB.
Every error carries a fix. See Errors for the shape.

Something unclear or wrong? The API answers every error with a fix, and so should these docs. Back to mailbay.dev