Reference
Sending mail
One endpoint does the sending. Everything else on this page is a field you can add to it.
Authentication
A bearer token on every request. Your API key is server-side only — it can send as any address on your verified domains.
Authorization: Bearer re_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxSend a message
/v1/emailsAPI keyReturns 202 as soon as the message is durably queued. It never waits for SMTP.
{
"from": "Acme <hello@example.com>",
"to": ["customer@gmail.com"],
"subject": "Your receipt",
"html": "<p>Thanks for your order.</p>",
"text": "Thanks for your order."
}| Field | Type | Required | Notes |
|---|---|---|---|
| from | string | yes | user@domain or Name <user@domain>. The domain must be verified. |
| to | string | string[] | yes | Up to 50 recipients across to, cc and bcc combined. |
| subject | string | yes | No line breaks — they would let a caller inject headers. |
| html | string | one of | HTML body. |
| text | string | one of | Plain-text body. Send both — HTML-only mail is filtered harder. |
| cc | string | string[] | no | |
| bcc | string | string[] | no | Never appears in the message headers. |
| replyTo | string | string[] | no | Where replies should go, if not the from address. |
| attachments | object[] | no | Up to 10 files. See below. |
| headers | object | no | Custom X-… headers. Reserved ones are rejected. |
| tags | object | no | String values, shown in the dashboard and useful for filtering. |
| metadata | object | no | Arbitrary JSON, stored and returned. Never sent to the recipient. |
| scheduledAt | string | no | RFC 3339. Omit to send now. |
Retrying a send is not safe
This endpoint is not idempotent — a replayed request sends the message again. Retry only when you know nothing was created: a request that got no response at all, a 429, or a 503 EMAIL_QUEUE_FAILED. Do not retry a 500; the message may have been queued before the failure, and a duplicate is worse than an error.
Attachments
Up to ten files per message, as base64. Three different things people mean by “an image in an email”, and they are not the same mechanism:
| What you want | How | Shows when images are blocked? |
|---|---|---|
| A file the recipient downloads | An attachment without cid | n/a |
| An image inside the message | An attachment with cid, referenced <img src="cid:logo"> | Yes — it is part of the message |
| An image hosted on your site | A plain <img src="https://…"> | No |
Prefer cid for anything that matters. Most clients block remote images until the reader allows them, so a hotlinked logo shows nothing on first open.
{
"from": "billing@example.com",
"to": ["customer@gmail.com"],
"subject": "Your invoice",
"html": "<p><img src=\"cid:logo\" width=\"140\" alt=\"Acme\"></p><p>Invoice attached.</p>",
"text": "Invoice attached.",
"attachments": [
{ "filename": "invoice.pdf", "content": "JVBERi0xLjQK…", "contentType": "application/pdf" },
{ "filename": "logo.png", "content": "iVBORw0KGgo…", "contentType": "image/png", "cid": "logo" }
]
}| Field | Required | Notes |
|---|---|---|
| filename | yes | No path separators. |
| content | yes | Base64 of the raw bytes. No data: prefix. |
| contentType | no | Defaults to application/octet-stream. |
| cid | no | Makes it an inline part. Must be unique in the message. |
There is no url option, on purpose
Some APIs let you pass a URL to fetch. This one does not: fetching a caller-supplied URL from inside our network is server-side request forgery — it would reach the cloud metadata endpoint and our own database. Read the file in your application and send the bytes.
Size
- Bodies plus decoded attachments must stay under 10 MB. Base64 inflates what you send by a third, but the limit counts real bytes.
- Receivers cap lower. Gmail rejects above 25 MB and many corporate gateways stop at 10 MB. Large files belong behind a link.
Examples
curl -X POST https://mail-api.scanmypass.com/v1/emails \
-H "Authorization: Bearer $MAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "hello@example.com",
"to": ["customer@gmail.com"],
"subject": "Welcome",
"html": "<h1>Welcome</h1>",
"text": "Welcome"
}'const response = await fetch("https://mail-api.scanmypass.com/v1/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MAIL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: "hello@example.com",
to: "customer@gmail.com",
subject: "Welcome",
html: "<h1>Welcome</h1>",
text: "Welcome",
}),
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.code}: ${error.message}`);
}
const { id } = await response.json();import os, requests
r = requests.post(
"https://mail-api.scanmypass.com/v1/emails",
headers={"Authorization": f"Bearer {os.environ['MAIL_API_KEY']}"},
json={
"from": "hello@example.com",
"to": ["customer@gmail.com"],
"subject": "Welcome",
"html": "<h1>Welcome</h1>",
"text": "Welcome",
},
timeout=30,
)
r.raise_for_status()
email_id = r.json()["id"]<?php
$ch = curl_init('https://mail-api.scanmypass.com/v1/emails');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('MAIL_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'from' => 'hello@example.com',
'to' => ['customer@gmail.com'],
'subject' => 'Welcome',
'html' => '<h1>Welcome</h1>',
'text' => 'Welcome',
]),
]);
$body = curl_exec($ch);Your logo and footer
Set once under Branding and added to every message you send, so you do not repeat it in every payload. The footer can be written separately for the HTML and plain-text parts, and {{logo_url}} in the HTML footer expands to your uploaded logo.