Sending API
The sending API lets your application send a transactional email through a Bridge with one HTTPS request. It's the alternative to SMTP for code you write yourself, serverless functions, and anything that finds an HTTP request easier than an SMTP connection.
Both methods go through the same Bridge. API messages use the Bridge's pool, follow its tracking switches and count towards its sending stats, exactly like SMTP messages.
Endpoint
POST https://api.mumara.com/sendEmail
- HTTPS only. A plain HTTP request is refused with
426. - One message, one recipient per request. To send the same email to several people, send one request for each.
- Content types:
application/json, ormultipart/form-datawhen you want to upload attachments as files.
The Sending API reference has every field, response and error in OpenAPI form, and the payloads of the webhook events that follow.
Authentication
Every request needs the Bridge's One Connect Key as a Bearer token:
Authorization: Bearer YOUR_ONE_CONNECT_KEY
To find the key:
- Go to Transactional → Bridges and click the Bridge's name.
- In the Method 2: Send via API panel, copy the One Connect Key.
Keep the key secret, like a password. Deleting the Bridge revokes the key. See Bridges.
Anyone who can see the key can send email as you. Never put it in browser JavaScript, mobile apps or public repositories. Keep it in an environment variable or your platform's secrets store.
The One Connect Key only sends email through its Bridge. Keys created under Setup → API Keys are for the Mumara REST API, which manages lists, contacts and campaigns, and they don't work here. See API Keys.
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
from | string | Yes | The sender, as orders@example.com or Example Store <orders@example.com>. The domain must be one of your verified sending domains. |
to | string | Yes | Exactly one recipient, as a plain address such as jane@example.com. Names and lists of addresses aren't accepted. |
subject | string | No | The subject line. Always set one: messages without a subject are far more likely to be filtered. |
html | string | Yes | The HTML body. It must contain <body> and </body> tags. |
text | string | No | A plain-text version of the message, shown by clients that don't display HTML. Recommended. |
amp | string | No | An AMP for Email version of the message, shown by email clients that support AMP. See AMP for Email. |
headers | object | No | Extra headers, as name and value pairs. In a multipart/form-data request, send this field as a JSON string. |
attachments | array | No | Files to attach. The format depends on the content type; see Attachments. |
The API doesn't have separate fields for CC, BCC or reply-to. To set a reply-to address, add a Reply-To header in headers.
The from address
The domain in from must be a sending domain you've verified under Setup → Sending Domains, and the match is exact: to send from alerts@mail.example.com, verify mail.example.com as a sending domain of its own. Any Bridge can use any of your verified domains. See Sending Domains.
Headers
Header names can contain only letters, numbers and hyphens, for example X-Order-Id. Use your own names for your own references. A name with any other character, such as a space, an underscore or a colon, is refused with 400 and Invalid header key, and nothing is sent.
Mumara ONE sets its own Message-ID, return path and DKIM signature on every message, as described in SMTP Settings. X-Var is reserved and is dropped from headers if you send it.
Your own headers reach the recipient, but they don't come back to you in delivery logs or webhook events. The exception is X-Track: set it to your own reference and its value is returned as the track field on delivery, open and click events. Since Mumara ONE replaces the Message-ID, this is how you tie its events to your records — see Match events to your own records with X-Track.
"headers": { "X-Track": "order-10025", "Reply-To": "support@example.com" }
Attachments
With application/json, send each attachment as an object in the attachments array:
| Property | Description |
|---|---|
filename | The file name the recipient sees, such as invoice-10025.pdf. The file type is worked out from the extension. |
content | The file's contents, base64-encoded. |
encoding | Optional. The encoding of content. Defaults to base64. |
A JSON request body can be up to 50 MB, and there's no limit per file beyond the message size below. Inline images, shown in the body through a cid: reference, aren't supported; link to images hosted on your own site instead.
With multipart/form-data, upload each file as a form part named attachments. Repeat the part to attach several files. Each file can be up to 10 MB, and each text field up to 1 MB.
The whole message, including attachments, can be up to 25 MB. Base64 makes attachments about a third larger than the original files, so leave room for that.
Examples
JSON
curl -X POST https://api.mumara.com/sendEmail \
-H "Authorization: Bearer YOUR_ONE_CONNECT_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "Example Store <orders@example.com>",
"to": "jane@example.com",
"subject": "Your order has shipped",
"text": "Good news: your order is on its way.",
"html": "<html><body><p>Good news: your order is on its way.</p></body></html>",
"headers": {
"X-Order-Id": "10025",
"Reply-To": "support@example.com"
},
"attachments": [
{
"filename": "receipt.txt",
"content": "VGhhbmsgeW91IGZvciB5b3VyIG9yZGVyLg==",
"encoding": "base64"
}
]
}'
Multipart form data with a file
curl -X POST https://api.mumara.com/sendEmail \
-H "Authorization: Bearer YOUR_ONE_CONNECT_KEY" \
--form-string 'from=Example Store <orders@example.com>' \
--form-string 'to=jane@example.com' \
--form-string 'subject=Your invoice' \
--form-string 'text=Your invoice is attached.' \
--form-string 'html=<html><body><p>Your invoice is attached.</p></body></html>' \
--form-string 'headers={"X-Order-Id": "10025"}' \
-F 'attachments=@invoice-10025.pdf'
Use --form-string for text fields. With plain -F, curl treats a value that starts with < or @ as a file to read.
Node.js
This example uses the built-in fetch in Node.js 18 and later:
async function sendOrderEmail() {
const response = await fetch('https://api.mumara.com/sendEmail', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.MUMARA_ONE_CONNECT_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: 'Example Store <orders@example.com>',
to: 'jane@example.com',
subject: 'Your order has shipped',
text: 'Good news: your order is on its way.',
html: '<html><body><p>Good news: your order is on its way.</p></body></html>',
headers: { 'X-Order-Id': '10025' },
}),
});
const result = await response.json();
if (result.status !== 'success') {
throw new Error(`Send failed (${response.status}): ${result.message}`);
}
return result;
}
The Bridge's details page has ready-made samples in cURL, PHP, NodeJS, Python and Bash, and an AMP for Email tab.
AMP for Email
AMP for Email lets a message carry interactive content, such as a carousel, an accordion or a form, for email clients that support it. To send one, add an amp field next to html. Mumara ONE builds the message with three versions, in the order AMP needs: text, then amp, then html.
- Clients that support AMP show the
ampversion. Every other client showshtml, sohtmlstays required. ampmust be a complete AMP for Email document:⚡4email(oramp4email) on the<html>tag,<script async src="https://cdn.ampproject.org/v0.js"></script>in the<head>, and at most 200,000 bytes. Anything else is refused with400and nothing is sent; see Errors.- Check the document is valid AMP before you send it, with the AMP validator or Gmail's AMP playground. Mumara ONE checks the points above, not every AMP rule.
curl -X POST https://api.mumara.com/sendEmail \
-H "Authorization: Bearer YOUR_ONE_CONNECT_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "Example Store <orders@example.com>",
"to": "jane@example.com",
"subject": "Your order has shipped",
"text": "Good news: your order is on its way.",
"html": "<html><body><p>Good news: your order is on its way.</p></body></html>",
"amp": "<!doctype html><html ⚡4email data-css-strict><head><meta charset=\"utf-8\"><script async src=\"https://cdn.ampproject.org/v0.js\"></script><style amp4email-boilerplate>body{visibility:hidden}</style></head><body><p>Good news: your order is on its way.</p></body></html>"
}'
In a multipart/form-data request, send amp with --form-string, like the other text fields.
Tracking in the AMP version
The Bridge's Track Opens and Track Clicks switches apply to the AMP version too, so opens and clicks are recorded whichever version the recipient sees:
- Track Opens adds a 1×1 tracking image as an
amp-imgbefore</body>, because AMP doesn't allow a plain<img>. - Track Clicks rewrites the
http://andhttps://links in<a href="...">tags. Links built by an AMP template, such ashref="{{url}}", and the addressesamp-formandamp-listcall, are left as they are. - Both always use HTTPS, as AMP requires. If the sending domain's tracking domain has no SSL, the AMP version uses the default tracking domain.
Tracked links and the tracking image make the AMP version bigger. If they would take it past 200,000 bytes, the AMP version is sent without open and click tracking.
Before you rely on AMP
- Gmail shows AMP only from registered senders. Register each sender address with Google (registration is per address, not per domain). Until then, Gmail shows the HTML version.
- Authentication must pass. Gmail also needs the message to pass SPF and a DKIM signature aligned with the
fromdomain. Mumara ONE signs messages from a verified sending domain with that domain's DKIM key; see Sending Domains. - Invalid AMP falls back to HTML, without any error. Forwarded emails lose the AMP version.
- Dynamic content comes from your own servers.
amp-listandamp-formrequests go to your endpoints, which must support CORS for AMP email. Mumara ONE doesn't host or proxy them. - Most email clients don't support AMP and always show the HTML version.
Over SMTP, add a text/x-amp-html part before the text/html part of a multipart/alternative message. It is tracked the same way.
Response
A successful request returns HTTP 200:
{
"status": "success",
"resCode": 250,
"message": "Email has been sent successfully",
"info": {
"messageId": "<5b0e8c7a-1f2d-4c3b-9a8e-7d6c5b4a3f21@example.com>",
"envelope": {
"from": "orders@example.com",
"to": ["jane@example.com"]
},
"accepted": ["jane@example.com"],
"rejected": [],
"response": "250 Message Queued (8E6C1B2A-4F3D-4E5A-9B7C-1D2E3F4A5B6C.1)",
"messageSize": 1834,
"envelopeTime": 38,
"messageTime": 112
}
}
| Field | Meaning |
|---|---|
status | success or error. Check this rather than resCode. |
resCode | 250 when the message was accepted, matching the SMTP code for success. |
info.accepted / info.rejected | The recipient address, in whichever list applies. |
info.response | The relay's final reply. |
info.messageSize | The size of the message, in bytes. |
info.envelopeTime / info.messageTime | How long, in milliseconds, the envelope and the message took to be accepted. |
"Accepted" means Mumara ONE has taken the message for delivery, not that it has reached the inbox. Delivery results appear in the Bridge's Sending Stats and in Analytics, and you can receive them in your own systems with webhooks under Actions → Webhooks.
The messageId in the response is assigned before the message reaches the relay. Mumara ONE then gives the message its own Message-ID, so the header the recipient receives is different, and so is the msg_id in the events you receive. Don't store this value as the key to your own records.
To match a message to your records, send an X-Track header with your own reference. Mumara ONE keeps it and returns it as the track field on every delivery, open and click event for that message. See Match events to your own records with X-Track.
Errors
Errors return "status": "error" and a message explaining what went wrong:
{
"status": "error",
"resCode": 400,
"message": "Invalid recipient email address: Jane <jane@example.com>"
}
| HTTP status | Message | Cause and fix |
|---|---|---|
400 | Invalid JSON format | The body isn't valid JSON. The errorDetail field shows where parsing failed, or says request entity too large when the body is over 50 MB. |
400 | Recipient email address is required | There's no to field. This also happens when the Content-Type header is missing, because the body isn't read. |
400 | Invalid recipient email address: ... | to isn't a single, plain address. Remove any display name and send one request per recipient. |
400 | Invalid headers format: ... | The headers field was sent as a string, as it always is in a multipart request, and the string isn't valid JSON. |
400 | Invalid header key: ... | A header name contains a character other than letters, numbers and hyphens, such as a space or an underscore. |
400 | amp is not an AMP for Email document: ... | The <html> tag of amp has no ⚡4email or amp4email. |
400 | amp is missing the AMP runtime: ... | amp doesn't load https://cdn.ampproject.org/v0.js in its <head>. |
400 | amp is ... bytes; AMP for Email allows at most 200000 | amp is over 200,000 bytes. |
400 | amp must be a string ... | amp was sent as something other than a string. |
401 | Unauthorized. Missing or invalid API Key. | The Authorization header is missing, or doesn't start with Bearer followed by the key. |
401 | Invalid API Key format. | The key is incomplete. Copy the whole One Connect Key again from the Bridge's details page. |
404 | Route does not exist | The path or method is wrong. Send a POST to /sendEmail. The body of this error has an error field instead of status and message. |
426 | Use HTTPS on port 443. Plain HTTP is not supported here. | The request was sent over plain HTTP. Use https://. This response is plain text, not JSON. |
500 | Failed to send email: HTML content is missing | There's no html field. |
500 | Failed to send email: HTML content is missing <body> tags | Wrap the HTML in <html><body> and </body></html>. |
500 | Failed to send email: followed by the relay's reply | The relay refused the message. The reply says why; see below. |
500 | An HTML error page | In a multipart request, a file is over 10 MB, a file part isn't named attachments, or a text field is over 1 MB. |
504 | An HTML error page | No answer within 50 seconds. This happens when a field has the wrong type, such as to sent as an array or an attachment without content. |
Not every error has the same shape, so check the HTTP status first:
Invalid JSON formathas noresCode.Invalid headers formatreturns the status inerrorCodeinstead ofresCode.
When the relay refuses a message, the reply after Failed to send email: gives the reason:
| The reply mentions | What it means |
|---|---|
Invalid login or 535 | The key is well formed but doesn't match a Bridge: it's mistyped, or the Bridge has been deleted. |
Null sender not allowed | There's no from field. |
is not authorized to relay emails | The domain in from isn't one of your verified sending domains. |
is disabled | The sending domain, or the Bridge, has been disabled. |
Insufficient transactional credits | Your transactional sending allowance has run out. See Plan and Usage. |
Your account is suspended | Contact Mumara support. |
Message too big! | The message, with its attachments, is over 25 MB. |
The API checks a request in this order, and a request with several problems reports the first one it finds:
- The body, and the
headersfield when it's sent as a string. - The recipient.
- The attachments.
- The HTML.
- The AMP version, if you sent one.
- The format of the key.
- The header names.
- Test mode, if you asked for it.
- The relay's checks: the key, the
fromdomain, the Bridge, your account and your sending allowance.
Retries, idempotency and rate limits
- No idempotency key. Every successful request sends a message. If a request times out, you can't tell whether the message was accepted, and sending it again may deliver it twice. For important messages, record what you've sent and check the delivery logs in Analytics before retrying.
- Retry
500relay errors only after fixing the cause. A refusal for an unverified domain, a wrong key or an exhausted allowance fails the same way until you fix it. - No rate limit. The API doesn't limit how many requests you make, and doesn't return rate-limit headers. Every message counts against your plan's transactional sending allowance, as with SMTP.
Test without sending
To check a request without sending any email, add an x-mode header with the value test:
"headers": { "x-mode": "test" }
Write the header name in lower case, exactly as shown; in any other case it isn't recognised and the message is sent. The API checks the body, the recipient, the attachments, the HTML, the AMP version, the format of the key and the header names, then returns this instead of sending:
{
"status": "success",
"resCode": 250,
"message": "Test mode detected. Skipping the delivery."
}
Test mode doesn't check that the key belongs to a Bridge, that the from domain is verified, or that you have sending allowance left. For a test that goes through all of those checks without delivering, use the X-Mode: mta-discard header described in SMTP Settings. It works in the headers field of an API request too, and the API then returns its normal success response, as if the message had been sent.
Tracking and engagement
API messages follow the Bridge's Track Opens and Track Clicks switches, the same as SMTP messages:
- Track Opens adds a tracking image before the closing
</body>tag, which every API message has. - Track Clicks rewrites the
http://andhttps://links in<a href="...">tags inhtml. - Write
</body>and<a hrefin lower case. Tracking doesn't find tags in upper case, such as</BODY>. - The
ampversion is tracked too, with anamp-imgand HTTPS links; see Tracking in the AMP version. - The
textpart is never changed. - Tracking also needs the sending domain in
fromto have its tracking domain enabled and set up.
See Bridges for the details and for when to leave tracking off.
Results for API messages are recorded against the Bridge, together with its SMTP messages:
- The Bridge's Sending Stats show totals for sent, delivered, bounced and complaints.
- Analytics shows opens, clicks, delivery logs, bounces and complaints message by message.
- Webhooks you set up under Actions → Webhooks push these events to your own systems.