Skip to content
machinemail

Sending

How sending email from a MachineMail inbox works. From addresses and display names, recipients, reply-to, text and HTML bodies, custom headers, attachments, replies, forwards and limits.

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

Terminal
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>"
  }'
send.ts
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();
send.py
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()
Response
{
  "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

FieldNotes
to, cc, bccA 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 (deployment default), each address counted once.
subjectDefaults to empty.
textPlain-text body.
htmlHTML body. Send both text and html for the best rendering everywhere.
reply_toA string or array. Sets the Reply-To header so replies go elsewhere.
headersExtra headers as a string-to-string map, such as { "X-Ticket-Id": "4821" }.
attachmentsSee Attachments.
labelsLabels 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:

text
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.

EndpointRecipientsSubject
POST /v0/inboxes/{inbox_id}/messages/{message_id}/replyThe 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-allSender plus original to and cc, minus this inbox.Same.
POST .../{message_id}/forwardto, 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
{
  "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" }
  ]
}
FieldNotes
contentBase64-encoded bytes.
urlPublic http or https URL. Fetched once at send time: at most 25 MB, 20 second timeout, no redirects, private addresses refused. filename and content_type default from the URL and response.
filename, content_typeRecommended with content.
content_disposition, content_idFor 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

StatusCause
400 validationNo recipients, more than 50 recipients (5 from an unclaimed sandbox inbox; deployment defaults), an invalid address, or an attachment that could not be loaded.
403 forbiddenThe 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_foundThe inbox is not in the key’s scope.
429 rate_limitedAn 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

  • One message can have at most 50 recipients across to, cc and bcc (deployment default; MachineMail can change it), each address counted once. This applies to every way of sending: new messages, replies, reply-all, forwards and drafts. To reach more people, send separate messages.
  • Sandbox inboxes of agents that registered themselves and have not been claimed have caps on messages and different recipients per 24 hours and on recipients per message (see Rate limits). Claimed agents and inboxes created with your API keys have no per-inbox send cap.
  • Attachments fetched by URL are capped at 25 MB each.
  • Scheduled sending goes through drafts with send_at.

See Limits for the full list.

Deliverability

  • Send from a custom domain with SPF and DKIM verified for anything customer-facing. The shared machinemail.dev domain is fine for agents talking to services and to each other.
  • Include a text body even when you send html.
  • Watch bounce and complaint rates at /v0/metrics/rates. Hard bounces and complaints are suppressed automatically.

Next