sending.md

Sending

Sending from a MachineMail inbox: from addresses, display names, recipients, reply-to, text and HTML bodies, headers, attachments, replies, forwards and limits.

Source: https://machinemail.dev/docs/concepts/sending

Every message is sent from an inbox. MachineMail builds the MIME message, sends it, stores a copy with the SENT label, threads it and emits message.sent.

Sending a message

curl

curl https://api.machinemail.dev/v0/inboxes/support-agent@machinemail.dev/messages/send \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": ["ada@example.com"],
    "subject": "Hello",
    "text": "Sent by an agent.",
    "html": "<p>Sent by an agent.</p>"
  }'

TypeScript

const inbox = encodeURIComponent('support-agent@machinemail.dev');
const res = await fetch(`https://api.machinemail.dev/v0/inboxes/${inbox}/messages/send`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.MACHINEMAIL_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    to: ['ada@example.com'],
    subject: 'Hello',
    text: 'Sent by an agent.',
    html: '<p>Sent by an agent.</p>',
  }),
});
const { message_id, thread_id } = await res.json();

Python

import os, requests

sent = requests.post(
    "https://api.machinemail.dev/v0/inboxes/support-agent@machinemail.dev/messages/send",
    headers={"Authorization": f"Bearer {os.environ['MACHINEMAIL_API_KEY']}"},
    json={
        "to": ["ada@example.com"],
        "subject": "Hello",
        "text": "Sent by an agent.",
        "html": "<p>Sent by an agent.</p>",
    },
).json()
{
  "message_id": "msg_01m4fy8h340000kzb2kp66c161",
  "thread_id": "thd_01m4fy8h340001pp6c74x4688h",
  "timestamp": "2026-10-09T09:00:15.844Z"
}

The response comes back with 201 once the message has been handed off for delivery. Whether it reached the recipient's server arrives later as a message.delivered, message.bounced or message.complained event, and as delivery_status on the stored message.

Request fields

Field Notes
to, cc, bcc A string or an array of addresses. Name <addr@example.com> is accepted. At least one recipient is required across the three, and at most 50, each address counted once.
subject Defaults to empty.
text Plain-text body.
html HTML body. Send both text and html for the best rendering everywhere.
reply_to A string or array. Sets the Reply-To header so replies go elsewhere.
headers Extra headers as a string-to-string map, such as { "X-Ticket-Id": "4821" }.
attachments See Attachments.
labels Labels for the stored copy, in addition to SENT.

From address and display name

The From address is always the inbox's address. You cannot send as another address. If the inbox has a display_name, it is used as the sender name:

From: Support agent <support-agent@machinemail.dev>

Change it with PATCH /v0/inboxes/{inbox_id} and display_name. To send from your own domain, create the inbox on a verified custom domain.

Threading

A new send starts a new thread unless you pass In-Reply-To or References in headers that match a message in the inbox. The easier way to continue a conversation is to use the reply endpoints, which set the headers for you.

Endpoint Recipients Subject
POST /v0/inboxes/{inbox_id}/messages/{message_id}/reply The original's Reply-To, else its sender. For a message you sent, its original recipients. Override with to. Re: added unless present.
POST .../{message_id}/reply-all Sender plus original to and cc, minus this inbox. Same.
POST .../{message_id}/forward to, cc or bcc (at least one required). Fwd: added; the original is quoted below your text.

Replies accept text, html, to, cc, bcc, reply_to, labels, attachments and headers. See Reply in thread.

Attachments

Each attachment is an object with either base64 content or a url that MachineMail fetches for you:

{
  "attachments": [
    { "filename": "invoice.pdf", "content_type": "application/pdf", "content": "JVBERi0xLjcK..." },
    { "url": "https://files.example.com/report.csv" },
    { "filename": "logo.png", "content_type": "image/png", "content": "iVBORw0KGgo...", "content_disposition": "inline", "content_id": "logo" }
  ]
}
Field Notes
content Base64-encoded bytes.
url Public http or https URL. Fetched once at send time: at most 10 MB, 20 second timeout, no redirects, private addresses refused. filename and content_type default from the URL and response.
filename, content_type Recommended with content.
content_disposition, content_id For inline images referenced as cid:logo in your HTML.

A URL that cannot be fetched fails the whole send with a validation error naming the attachment, for example attachments[1].url. See the attachments guide.

When sending is refused

Status Cause
400 validation No recipients, more than 50 recipients (5 from an unclaimed sandbox inbox), an invalid address, or an attachment that could not be loaded.
403 forbidden The inbox is paused, a recipient is suppressed, a recipient is blocked by a send list, or the key lacks message_send (or message_reply for replies).
404 not_found The inbox is not in the key's scope.
429 rate_limited An unclaimed agent's sandbox inbox has used its sends or its different recipients for the past 24 hours (Retry-After says when the next send fits).

All recipients are checked before anything is sent; if one is refused, none receive the message.

Limits

See Limits for the full list.

Deliverability

Next