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.
| Field | Type | Description |
|---|---|---|
| to, cc, bcc | string | string[] | One address or a list. Plain addresses only, no display names. At least one recipient, at most 50 across all three. |
| reply_to | string | string[] | Sets the Reply-To header. |
| subject | string | One line, at most 998 characters. Defaults to empty. |
| text, html | string | The body. At least one of them is required. |
| headers | object | Extra 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. |
| attachments | Attachment[] | 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.
| Address | Event | Result |
|---|---|---|
| delivered@test.mailbay.dev | message.delivered | Accepted by the recipient's server. |
| bounced@test.mailbay.dev | message.bounced | A permanent 550 5.1.1 bounce. |
| complained@test.mailbay.dev | message.complained | Delivered, 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.
| Request | Status | Result |
|---|---|---|
| Same key, same body | 201 | Returns the message stored by the first request. Nothing is sent again. |
| Same key, different body | 409 | idempotency_conflict. Use a new key for a different request. |
| Same key, first still running | 409 | idempotency_in_progress. Retry in a moment for its result. |
| First request failed | The 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.
| Field | Type | Description |
|---|---|---|
| text, html | string | The body. At least one is required. |
| reply_all | boolean | Also copy everyone else on the original message. Defaults to false. |
| headers, attachments | Same 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.
| Status | Event | Meaning |
|---|---|---|
| queued | message.queued | Stored and handed to the mail server. |
| sent | The mail server accepted it for every recipient. | |
| delivered | message.delivered | Every recipient's server accepted it. |
| deferred | message.deferred | A temporary failure; the mail server keeps retrying. |
| bounced | message.bounced | A recipient's server refused it permanently. |
| failed | message.failed | The mail server did not accept it, or gave up retrying. |
| complained | message.complained | A 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 ContentPlan 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
| Code | Status | Meaning |
|---|---|---|
| sandbox_recipient_not_allowed | 403 | The 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_send | 403 | The Catch-all only receives. Create an inbox with the address you want to send from. |
| recipient_suppressed | 400 | A recipient hard bounced or complained before. Remove it, or delete the suppression if you are sure the address works again. |
| sending_paused | 403 | Sending 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_reached | 402 | The plan's monthly recipients are used up. Upgrade, or wait for the first of next month (UTC). See Pricing. |
| daily_send_limit_reached | 402 | Orgs younger than 7 days send to at most 100 recipients a day (UTC). The limit lifts on day 8. |
| transport_unavailable | 403 | The server has no mail transport configured. Self hosted operators set KUMO_INJECT_URL. |
| idempotency_conflict | 409 | The Idempotency-Key was already used with a different request body. |
| idempotency_in_progress | 409 | A request with the same key is still running. Retry in a moment. |
| too_many_recipients | 400 | More than 50 addresses across to, cc and bcc (none at all is missing_recipients). |
| missing_body | 400 | Neither text nor html was given. |
| invalid_address | 400 | An address is not a plain address such as user@example.com. |
| reserved_header | 400 | A header in headers is one Mailbay sets itself. Use the matching field. |
| invalid_attachment | 400 | An attachment has no filename, a bad content type or content that is not base64. |
| message_too_large | 400 | The encoded message is over 25 MB. |
fix. See Errors for the shape.