Skip to main content

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, or multipart/form-data when 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:

  1. Go to Transactional → Bridges and click the Bridge's name.
  2. 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.

Call the API from your server only

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.

Not the same as your API key

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​

FieldTypeRequiredDescription
fromstringYesThe sender, as orders@example.com or Example Store <orders@example.com>. The domain must be one of your verified sending domains.
tostringYesExactly one recipient, as a plain address such as jane@example.com. Names and lists of addresses aren't accepted.
subjectstringNoThe subject line. Always set one: messages without a subject are far more likely to be filtered.
htmlstringYesThe HTML body. It must contain <body> and </body> tags.
textstringNoA plain-text version of the message, shown by clients that don't display HTML. Recommended.
ampstringNoAn AMP for Email version of the message, shown by email clients that support AMP. See AMP for Email.
headersobjectNoExtra headers, as name and value pairs. In a multipart/form-data request, send this field as a JSON string.
attachmentsarrayNoFiles 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:

PropertyDescription
filenameThe file name the recipient sees, such as invoice-10025.pdf. The file type is worked out from the extension.
contentThe file's contents, base64-encoded.
encodingOptional. 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 amp version. Every other client shows html, so html stays required.
  • amp must be a complete AMP for Email document: ⚡4email (or amp4email) 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 with 400 and 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-img before </body>, because AMP doesn't allow a plain <img>.
  • Track Clicks rewrites the http:// and https:// links in <a href="..."> tags. Links built by an AMP template, such as href="{{url}}", and the addresses amp-form and amp-list call, 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.
Leave room for tracking

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 from domain. 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-list and amp-form requests 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
}
}
FieldMeaning
statussuccess or error. Check this rather than resCode.
resCode250 when the message was accepted, matching the SMTP code for success.
info.accepted / info.rejectedThe recipient address, in whichever list applies.
info.responseThe relay's final reply.
info.messageSizeThe size of the message, in bytes.
info.envelopeTime / info.messageTimeHow 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.

About messageId

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 statusMessageCause and fix
400Invalid JSON formatThe 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.
400Recipient email address is requiredThere's no to field. This also happens when the Content-Type header is missing, because the body isn't read.
400Invalid recipient email address: ...to isn't a single, plain address. Remove any display name and send one request per recipient.
400Invalid 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.
400Invalid header key: ...A header name contains a character other than letters, numbers and hyphens, such as a space or an underscore.
400amp is not an AMP for Email document: ...The <html> tag of amp has no ⚡4email or amp4email.
400amp is missing the AMP runtime: ...amp doesn't load https://cdn.ampproject.org/v0.js in its <head>.
400amp is ... bytes; AMP for Email allows at most 200000amp is over 200,000 bytes.
400amp must be a string ...amp was sent as something other than a string.
401Unauthorized. Missing or invalid API Key.The Authorization header is missing, or doesn't start with Bearer followed by the key.
401Invalid API Key format.The key is incomplete. Copy the whole One Connect Key again from the Bridge's details page.
404Route does not existThe path or method is wrong. Send a POST to /sendEmail. The body of this error has an error field instead of status and message.
426Use 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.
500Failed to send email: HTML content is missingThere's no html field.
500Failed to send email: HTML content is missing <body> tagsWrap the HTML in <html><body> and </body></html>.
500Failed to send email: followed by the relay's replyThe relay refused the message. The reply says why; see below.
500An HTML error pageIn a multipart request, a file is over 10 MB, a file part isn't named attachments, or a text field is over 1 MB.
504An HTML error pageNo 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 format has no resCode.
  • Invalid headers format returns the status in errorCode instead of resCode.

When the relay refuses a message, the reply after Failed to send email: gives the reason:

The reply mentionsWhat it means
Invalid login or 535The key is well formed but doesn't match a Bridge: it's mistyped, or the Bridge has been deleted.
Null sender not allowedThere's no from field.
is not authorized to relay emailsThe domain in from isn't one of your verified sending domains.
is disabledThe sending domain, or the Bridge, has been disabled.
Insufficient transactional creditsYour transactional sending allowance has run out. See Plan and Usage.
Your account is suspendedContact 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:

  1. The body, and the headers field when it's sent as a string.
  2. The recipient.
  3. The attachments.
  4. The HTML.
  5. The AMP version, if you sent one.
  6. The format of the key.
  7. The header names.
  8. Test mode, if you asked for it.
  9. The relay's checks: the key, the from domain, 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 500 relay 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:// and https:// links in <a href="..."> tags in html.
  • Write </body> and <a href in lower case. Tracking doesn't find tags in upper case, such as </BODY>.
  • The amp version is tracked too, with an amp-img and HTTPS links; see Tracking in the AMP version.
  • The text part is never changed.
  • Tracking also needs the sending domain in from to 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.