Send email from a domain the organization has verified. Sending domains are added and verified in the console; this API sends from them and reports what happened to each message.
One recipient per call. to is a single address and there is no cc or bcc. For several recipients, call this once per person: a single message addressed to many is rejected as a whole if one address fails, and it is billed per recipient anyway.
/organizations/:orgId/mailsSend one message. Requires a runtime-scope key or a Bearer token. Provide html, text, or both.
{
"from": "hello@yourdomain.com",
"to": "customer@example.com",
"subject": "Welcome aboard",
"html": "<p>Thanks for signing up.</p>",
"text": "Thanks for signing up.",
"tag": "signup" // Optional label of your own, returned as-is
}{
"mail": {
"id": "a1b2c3d4e5f6...",
"from": "hello@yourdomain.com",
"to": "cu******@example.com",
"status": "sent",
"tag": "signup",
"deliveredAt": null,
"openedAt": null,
"error": null,
"createdAt": "2026-08-06T...",
"updatedAt": "2026-08-06T..."
}
}The recipient comes back masked. We store it that way, so a leaked log or a stolen key never yields a customer list. Keep your own copy if you need the full address.
The domain in from must match a verified domain exactly. A subdomain of a verified domain is not accepted.
400 Missing or malformed from, to, subject, or body
403 That sending domain is not registered to this organization
409 Sending domain is not ready (status: pending | failed | suspended)
429 Send rate limit exceeded (5 messages/second). See Retry-After
502 Upstream send failed; the message row is kept with status "failed"On 429, wait for Retry-After and send again. Do not retry immediately in a loop: the cap is per organization and per second, and hammering it only lengthens the queue you are in.
/organizations/:orgId/mailsList what the organization has sent, newest first. Use limit (1-100, default 50) and cursor for pagination. Requires a runtime-scope key or a Bearer token.
{
"mails": [
{
"id": "a1b2c3d4e5f6...",
"from": "hello@yourdomain.com",
"to": "cu******@example.com",
"status": "delivered",
"tag": "signup",
"deliveredAt": "2026-08-06T...",
"openedAt": null,
"error": null,
"createdAt": "2026-08-06T...",
"updatedAt": "2026-08-06T..."
}
],
"nextCursor": 41
}Pass nextCursor back as cursor for the following page. It is null on the last page.
/organizations/:orgId/mails/:mailIdFetch one message by the id returned when it was sent. Requires a runtime-scope key or a Bearer token.
sent Accepted for delivery. Every message starts here.
delivered The receiving server accepted it. deliveredAt is set.
bounced It could not be delivered. Stop sending to this address.
complained The recipient marked it as spam. Stop sending to this address.
rejected A virus was found in the message and it was not delivered.
failed We could not hand it over at all. See error.Status updates arrive within seconds of the event, so a message read back immediately after sending is usually still "sent". Poll, or read it later.
openedAt is set when the recipient opens the message. Treat it as a hint and not a measurement: many mail clients block the check entirely, and some preload it without a person ever looking.
/organizations/:orgId/mails/guideReturns MAIL.md, a written guide to sending, with the organization’s sending domains and their current status filled in. Requires a deploy-scope key or a Bearer token, so a runtime key gets 403.
{ "guide": "# Sending email with JustDeploy\n..." }Each message costs $0.0005 and is drawn from the same credit as compute and storage. Bounces and spam complaints are counted per organization, and sending is stopped for the whole organization if either gets too high.