Skip to content
cMailerEmail APIv1
Base URLhttps://api.cmailer.net
Portal sign in
On this pageOverview

Email API#

Send email from your cMailer domains with one HTTPS request. The mail server DKIM-signs and delivers it the same way as mail sent from a mailbox, and every send is logged in the portal.

Base URL
https://api.cmailer.net
Format
JSON request and response bodies, UTF-8
Authentication
Authorization: Bearer cm_…
Endpoints
GET /v1 lists them and needs no key.

Quick start#

  1. Sign in to the portal and create a key under Access → API keys with sending access for your domain. The key is shown once, so store it somewhere safe; the examples read it from CMAILER_API_KEY.
  2. Send a message with POST /v1/emails. The from address must be one the key can send from (see Sender addresses).
  3. The response has an id. Fetch GET /v1/emails/{id} to see what happened to the message, or find it in the portal under Mail → Outbound.
Send your first email
Request
curl https://api.cmailer.net/v1/emails \
  -H "Authorization: Bearer $CMAILER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme <hello@acme.example>",
    "to": ["jane@example.com"],
    "subject": "Your Acme account is ready",
    "html": "<p>Hi Jane, welcome aboard.</p>"
  }'
Response · 200
{
  "id": "3f2b1c7e-6d4a-4f0b-9c1e-8a7d2b5e4c31",
  "status": "queued",
  "queue_id": "4cKq2mT7Xz",
  "message_id": "<3f2b1c7e-6d4a-4f0b-9c1e-8a7d2b5e4c31@acme.example>",
  "created_at": "2026-09-23T09:14:03.000Z"
}

Authentication#

Send the key as a bearer token on every request. Keys start with cm_, are shown once when they’re created and are stored only as a hash, so a lost key can’t be recovered: create a new one and revoke the old one. A revoked or disabled key fails straight away with 401 invalid_api_key.

A key only covers domains its owner can still manage. If the owner loses access to a domain, or the domain is removed from cMailer, the key loses it too, and a key left with no domains stops working.

Key types#

What each kind of key can do
KeyCan send asEmail APIManagement API
Domain key, sending onlyAny address at its domains that the mail server knows (Sender addresses)YesNo
Domain key, full accessThe same as sending onlyYesYes
Domain key, management onlyNothingNoYes
Inbox keyOnly the exact addresses chosen for it, up to 50YesNo
Older key starting ext_NothingNoLists domains and forwards; turns a forward on or off

A send-only domain can only be given to a key with sending access: this server holds no mailboxes or forwards for it to manage.

Sender addresses#

The mail server only accepts senders it knows, so the from address must already exist:

  • On a hosted domain, an active mailbox or forwarding address. If the domain has an active catch-all, any address at it works.
  • On a send-only domain, an address on the domain’s approved senders list, and the domain must be verified.
  • With an inbox key, only the addresses chosen for the key.

Anything else returns 403 sender_not_allowed and nothing is sent, as does sending from a suspended domain. Add addresses and approved senders in the portal. GET /v1/domains shows which domains and addresses a key covers.

Header
Authorization: Bearer cm_0f3a…
Response · 401
{
  "statusCode": 401,
  "name": "missing_api_key",
  "message": "Missing API key. Use: Authorization: Bearer cm_xxx"
}
Response · 403
{
  "statusCode": 403,
  "name": "sender_not_allowed",
  "message": "billing@acme.example does not exist on acme.example. Create it as a mailbox or forwarding address in the portal first, or send from an existing address."
}

Send an email#

POST/v1/emails

Builds the message, hands it to the mail server and returns once the server has queued it. Delivery to each recipient happens afterwards; retrieve the email to see the outcome.

Limits50 recipients across to, cc and bcc20 attachments10 MB per encoded messagecounts 1 of 120 requests a minute

Body#

A JSON object with these fields. Unknown fields are rejected, so a typo can’t silently drop content.

POST /v1/emails body fields
FieldDescription
fromrequiredstring | object"Name <user@domain>", a bare address or { "name", "email" }. Must be an address this key can send from (Sender addresses).
torequiredstring | arrayOne or more recipients, each "Name <user@example.com>", a bare address or { "name", "email" }. One string may hold several comma-separated addresses. Repeated addresses are sent once.
ccoptionalstring | arrayCopy recipients, written the same way as to.
bccoptionalstring | arrayRecipients who get the message but never appear in its headers.
reply_tooptionalstring | arrayWhere replies go, when that differs from from. replyTo is accepted too.
subjectrequiredstringUp to 998 characters. Line breaks and control characters are rejected; runs of whitespace become one space.
htmlhtml or textstringHTML body. Send html, text or both; with both, mail clients show the part they can display.
texthtml or textstringPlain-text body.
headersoptionalobjectUp to 20 custom headers, such as { "X-Entity-Ref-ID": "order-2048" }. Names are up to 80 letters, digits and hyphens; values are up to 1,000 characters. Headers the mail system sets are rejected: From, To, Cc, Bcc, Subject, Date, Message-ID, Reply-To, Sender, Return-Path, Content-Type, Content-Transfer-Encoding, MIME-Version, DKIM-Signature, Received, Authentication-Results, Received-SPF, X-Original-To, Delivered-To.
attachmentsoptionalarrayUp to 20 files. See Attachments.
tagsoptionalarrayUp to 10 { "name", "value" } pairs for your own records, each 1 to 256 letters, digits, _ or -. Stored in the send log and returned with the email; never added to the message.

Response#

A 200 means the mail server accepted the message into its queue.

POST /v1/emails response fields
FieldMeaning
idThe email’s ID in this API, for GET /v1/emails/{id}.
statusqueued: the mail server accepted the message. A refusal is an error instead (below). pending only appears when an idempotent replay catches the first request still being handed over.
queue_idThe Postfix queue ID from the server’s reply, for matching server logs or the portal’s message journey. null if the reply didn’t include one.
message_idThe Message-ID header written into the email: <{id}@{sender’s domain}>.
created_atWhen the request was accepted, in ISO 8601 UTC.

Errors#

Errors from POST /v1/emails
ErrorWhen
400 validation_errorA field is missing, malformed or unknown, or Idempotency-Key is invalid. The message names the field.
403 sender_not_allowedThe from address can’t be used by this key (Sender addresses).
409 conflictAn Idempotency-Key clash (Idempotency).
413 message_too_largeThe encoded message is over 10 MB.
502 smtp_errorThe mail server refused the message; message includes its reply. The body has the id of the logged attempt.
503 mail_server_unavailableThe mail server couldn’t be reached, so nothing was sent. Retry shortly, with the same Idempotency-Key if you used one.

Idempotency#

Retrying after a network failure can send a message twice. Add an Idempotency-Key header (1 to 255 letters, digits, ., :, _ or -) and the API remembers the result for that key:

  • If the first request was accepted, or is still being handed over, a repeat returns the original result with an Idempotent-Replayed: true header and sends nothing. The repeat’s body is still validated, but not compared with the first.
  • If the first request failed with 502 or 503, a repeat with the same sender, subject and recipients sends it again, keeping the same id and Message-ID. A repeat with a different email returns 409 conflict, as does a repeat sent while that retry is still running.
  • Keys belong to the API key that used them and last as long as the send log: 90 days.
  • Batches ignore Idempotency-Key.
Send an email
Request
curl https://api.cmailer.net/v1/emails \
  -H "Authorization: Bearer $CMAILER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-2048-confirmation" \
  -d '{
    "from": "Acme Orders <orders@acme.example>",
    "to": ["Jane Doe <jane@example.com>"],
    "cc": ["accounts@acme.example"],
    "reply_to": "support@acme.example",
    "subject": "Order #2048 confirmed",
    "html": "<h1>Thanks, Jane</h1><p>Your order ships tomorrow.</p>",
    "text": "Thanks, Jane. Your order ships tomorrow.",
    "headers": { "X-Entity-Ref-ID": "order-2048" },
    "tags": [{ "name": "category", "value": "order_confirmation" }]
  }'
Response · 200
{
  "id": "3f2b1c7e-6d4a-4f0b-9c1e-8a7d2b5e4c31",
  "status": "queued",
  "queue_id": "4cKq2mT7Xz",
  "message_id": "<3f2b1c7e-6d4a-4f0b-9c1e-8a7d2b5e4c31@acme.example>",
  "created_at": "2026-09-23T09:14:03.000Z"
}

Attachments and inline images#

Attach files to POST /v1/emails, or to any email in a batch, as base64 in the attachments array.

Limits20 attachments per email10 MB per encoded message, attachments included
Attachment object fields
FieldDescription
filenamerequiredstringThe name the recipient sees. Only the base name is kept (any path is dropped), up to 255 characters.
contentrequiredstringThe file’s bytes, base64-encoded. Whitespace is ignored.
content_typeoptionalstringMIME type, such as application/pdf. Worked out from filename when left out. contentType is accepted too.
content_idoptionalstringMakes the file an inline image the HTML can show as cid:…. Must look like logo@acme.example. contentId is accepted too.

Base64 makes files about a third larger, and the size limit applies to the encoded message, so keep the files themselves well under 10 MB. Remote URLs and file paths (url, href, path) are rejected: send the bytes.

Inline images#

Give the attachment a content_id and use it in the HTML as <img src="cid:logo@acme.example">. Attachments with a content_id are sent inline; the others are ordinary attachments.

Attach files
Attachment
curl https://api.cmailer.net/v1/emails \
  -H "Authorization: Bearer $CMAILER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme Orders <orders@acme.example>",
    "to": ["jane@example.com"],
    "subject": "Order 2048 summary",
    "text": "Your order summary is attached.",
    "attachments": [
      {
        "filename": "order-2048.txt",
        "content": "T3JkZXIgMjA0OAoyIHNvdXJkb3VnaCBsb2F2ZXMsIDEgbGVtb24gY2FrZQpTaGlwcyB0b21vcnJvdy4K",
        "content_type": "text/plain"
      }
    ]
  }'
Inline image
curl https://api.cmailer.net/v1/emails \
  -H "Authorization: Bearer $CMAILER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme <hello@acme.example>",
    "to": ["jane@example.com"],
    "subject": "Welcome to Acme",
    "html": "<p>Welcome aboard.</p><img src=\"cid:logo@acme.example\" alt=\"Acme\">",
    "attachments": [
      {
        "filename": "logo.png",
        "content": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
        "content_type": "image/png",
        "content_id": "logo@acme.example"
      }
    ]
  }'

Send a batch#

POST/v1/emails/batch

Sends up to 100 separate emails in one request.

Limits100 emails per requesteach email counts 1 of 120 requests a minute

The body is a JSON array of the objects POST /v1/emails accepts. The whole batch is checked first, senders included: if any email is invalid, the request fails with its position (counting from 0) in the message, such as [3] subject: is required, and nothing is sent.

Then each email is handed to the mail server in turn and reported on its own, so one refusal doesn’t undo the others. The batch is charged against the rate limit up front, one per email; if there isn’t room for all of it, the whole batch gets 429 and nothing is sent. Idempotency-Key doesn’t apply to batches.

Response#

data lists one result per email, in the order they were sent.

POST /v1/emails/batch result fields
FieldMeaning
idThe email’s ID, or null if it failed before it was logged.
statusqueued or failed.
errorOn failures only: { "name", "message" }, as in Errors.
Send a batch
Request
curl https://api.cmailer.net/v1/emails/batch \
  -H "Authorization: Bearer $CMAILER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "from": "Acme <hello@acme.example>",
      "to": ["sam@example.com"],
      "subject": "Your sign-in code",
      "text": "Your code is 482913."
    },
    {
      "from": "Acme <hello@acme.example>",
      "to": ["lee@example.com"],
      "subject": "Your sign-in code",
      "text": "Your code is 730155."
    }
  ]'
Response · 200
{
  "data": [
    { "id": "9c0d4e2a-51b7-4c38-8f7e-2d64a1b3c905", "status": "queued" },
    {
      "id": "e41f0b6c-2a9d-4c57-b8e3-7f1d0a5c6b29",
      "status": "failed",
      "error": {
        "name": "smtp_error",
        "message": "The mail server did not accept the message: 452 4.3.1 Insufficient system storage"
      }
    }
  ]
}

Retrieve an email#

GET/v1/emails/{id}

Returns an email’s send-log record and, where the server’s delivery log can be read, what happened to it after it was queued.

Limitscounts 1 of 120 requests a minute

Bodies and attachments are never stored, so they can’t be returned. Domain keys can read any email sent from their domains, whichever key sent it; inbox keys only see emails they sent. Anything else is 404 not_found.

Response#

Email object fields
FieldMeaning
objectAlways email.
idThe email’s ID.
from, to, cc, bcc, reply_toAs sent, each address written as Name <user@domain> or a bare address. from is a string; the others are arrays.
subjectAs sent.
statusThe send-log state: queued (accepted by the mail server), failed (refused, or the server couldn’t be reached) or pending (being handed over).
queue_id, message_idAs in the send response.
errorWhy a failed send didn’t go, or null.
size_bytesSize of the encoded message.
attachmentsHow many files were attached. The files themselves aren’t stored.
tags, idempotency_keyAs sent, or [] and null.
created_atWhen the request was accepted, in ISO 8601 UTC.
deliveryWhat happened after the mail server queued it: see Delivery status. Only on this endpoint.

Delivery status#

delivery has a status, a detail (the receiving server’s reply or an explanation), checked_at and a source: postfix-log, api (the send log itself) or none. When the log was read for this request it also has events, the number of log entries found.

delivery.status values
delivery.statusMeaning
deliveredEvery recipient’s mail server accepted the message.
partially_deliveredDelivered to some recipients; the others have no outcome in the log yet.
deferredA recipient’s mail server asked cMailer to try again later, and none refused. Postfix keeps retrying on its own.
bouncedAt least one recipient’s mail server refused the message, even if others accepted it. detail has its reply.
queuedAccepted by the mail server; no delivery attempt logged yet.
unknownNo outcome to report: the email isn’t in the retained log, is more than 7 days old, or hasn’t reached the mail server yet. This isn’t proof it was lost.
unavailableThis server doesn’t let the API read its Postfix log, or the log couldn’t be read. Only the acceptance in status is known.
not_sentThe mail server refused the message or couldn’t be reached when it was sent (status is failed).

Outcomes are read from the log when you ask and reused for a minute, so polling more often returns the same answer. delivered and bounced are final and never re-read. Emails older than 7 days aren’t looked up.

Retrieve an email
Request
curl https://api.cmailer.net/v1/emails/3f2b1c7e-6d4a-4f0b-9c1e-8a7d2b5e4c31 \
  -H "Authorization: Bearer $CMAILER_API_KEY"
Response · 200
{
  "object": "email",
  "id": "3f2b1c7e-6d4a-4f0b-9c1e-8a7d2b5e4c31",
  "from": "Acme Orders <orders@acme.example>",
  "to": ["Jane Doe <jane@example.com>"],
  "cc": ["accounts@acme.example"],
  "bcc": [],
  "reply_to": ["support@acme.example"],
  "subject": "Order #2048 confirmed",
  "status": "queued",
  "queue_id": "4cKq2mT7Xz",
  "message_id": "<3f2b1c7e-6d4a-4f0b-9c1e-8a7d2b5e4c31@acme.example>",
  "error": null,
  "size_bytes": 4821,
  "attachments": 0,
  "tags": [{ "name": "category", "value": "order_confirmation" }],
  "idempotency_key": "order-2048-confirmation",
  "created_at": "2026-09-23T09:14:03.000Z",
  "delivery": {
    "status": "delivered",
    "detail": "250 2.0.0 OK 1790154911 x12si9876543 - gsmtp",
    "checked_at": "2026-09-23T09:15:11.000Z",
    "source": "postfix-log",
    "events": 4
  }
}

List emails#

GET/v1/emails

Lists emails newest first, a page at a time.

Limitsup to 200 per pagecounts 1 of 120 requests a minute

Domain keys see every email sent from their domains; inbox keys see their own. Entries have the same fields as Retrieve an email without delivery: fetch an email by id for its delivery outcome.

Query parameters#

GET /v1/emails query parameters
ParameterDescription
limitoptionalintegerEmails per page, 1 to 200. Defaults to 50; values outside the range are brought within it.
statusoptionalstringOnly emails in this send-log state: queued, failed or pending.
cursoroptionalstringThe next_cursor from the previous page.

Response#

GET /v1/emails response fields
FieldMeaning
objectAlways list.
dataEmails, newest first.
next_cursorPass as cursor for the next page, or null on the last page.
has_moreWhether there’s another page.
List emails
Request
curl "https://api.cmailer.net/v1/emails?limit=25&status=queued" \
  -H "Authorization: Bearer $CMAILER_API_KEY"
Response · 200
{
  "object": "list",
  "data": [
    {
      "object": "email",
      "id": "3f2b1c7e-6d4a-4f0b-9c1e-8a7d2b5e4c31",
      "from": "Acme Orders <orders@acme.example>",
      "to": ["Jane Doe <jane@example.com>"],
      "cc": ["accounts@acme.example"],
      "bcc": [],
      "reply_to": ["support@acme.example"],
      "subject": "Order #2048 confirmed",
      "status": "queued",
      "queue_id": "4cKq2mT7Xz",
      "message_id": "<3f2b1c7e-6d4a-4f0b-9c1e-8a7d2b5e4c31@acme.example>",
      "error": null,
      "size_bytes": 4821,
      "attachments": 0,
      "tags": [{ "name": "category", "value": "order_confirmation" }],
      "idempotency_key": "order-2048-confirmation",
      "created_at": "2026-09-23T09:14:03.000Z"
    }
  ],
  "next_cursor": "MTc5MDE1NDg0MzozZjJiMWM3ZS02ZDRhLTRmMGItOWMxZS04YTdkMmI1ZTRjMzE",
  "has_more": true
}

Domains#

GET/v1/domains

Shows what the calling key can send: its scope, its permissions and the domains or addresses it can send from.

Limitsneeds sending accesscounts 1 of 120 requests a minute

Domains and addresses are added and verified in the portal, not through this API.

Response#

GET /v1/domains response fields
FieldMeaning
key.nameThe key’s name in the portal.
key.scopedomain for a domain key, mailbox for an inbox key.
key.permissionssend, manage or both.
data[].nameA domain the key can send from.
data[].sendersFor a domain key, *@domain: any address there that the mail server knows (Sender addresses). For an inbox key, its exact addresses.
Domains
Request
curl https://api.cmailer.net/v1/domains \
  -H "Authorization: Bearer $CMAILER_API_KEY"
Response · 200, domain key
{
  "object": "list",
  "key": { "name": "Order emails", "scope": "domain", "permissions": ["send"] },
  "data": [
    {
      "object": "domain",
      "name": "acme.example",
      "senders": ["*@acme.example"]
    }
  ]
}
Response · 200, inbox key
{
  "object": "list",
  "key": {
    "name": "Support desk",
    "scope": "mailbox",
    "permissions": ["send"]
  },
  "data": [
    {
      "object": "domain",
      "name": "acme.example",
      "senders": ["support@acme.example"]
    }
  ]
}

Errors#

Errors use the HTTP status code and a JSON body with statusCode, name (stable, for your code to check) and message (for a developer; it names the field at fault where there is one). A send that the mail server refused or couldn’t take also includes the id of the logged attempt.

Email API errors
StatusnameWhen
400validation_errorA field is missing, malformed or unknown, the body isn’t valid JSON, or a header or query value is invalid. Nothing was sent.
401missing_api_keyNo Authorization: Bearer header.
401invalid_api_keyThe key is malformed, unknown, revoked or disabled, or its owner no longer exists.
403forbiddenThe key doesn’t have the access this API needs (Key types), or has no domains left.
403sender_not_allowedThis key can’t send from the from address (Sender addresses).
404not_foundNo email with that ID is visible to this key, or there’s no such route.
409conflictAn Idempotency-Key clash (Idempotency).
413message_too_largeThe encoded message is over 10 MB.
429rate_limit_exceededThe key is over its rate limit. Wait for Retry-After seconds.
500internal_server_errorSomething failed on cMailer’s side. Retry, and contact support if it keeps happening.
502smtp_errorThe mail server refused the message; message includes its reply. Logged as failed.
503mail_server_unavailableThe mail server couldn’t be reached. Nothing was sent; retry shortly.
Response · 400
{
  "statusCode": 400,
  "name": "validation_error",
  "message": "to[0]: \"jane@example\" is not a valid email address"
}
Response · 502
{
  "statusCode": 502,
  "name": "smtp_error",
  "message": "The mail server did not accept the message: 452 4.3.1 Insufficient system storage",
  "id": "3f2b1c7e-6d4a-4f0b-9c1e-8a7d2b5e4c31"
}

Limits#

This server’s current limits, read from its configuration. They’re also published as JSON at GET /docs/limits, which needs no key.

Email API limits
LimitValue
Recipients per email, across to, cc and bcc50
Attachments per email20
Encoded message size, attachments included10 MB (10,485,760 bytes)
Emails per batch100
Requests per key per minute120
Custom headers per email20
Tags per email10
Subject length998 characters
Emails per list page200 (default 50)
Send log and idempotency keys kept for90 days
Delivery lookups coverthe last 7 days
Limits as JSON
Request
curl https://api.cmailer.net/docs/limits
Response · 200
{
  "base_url": "https://api.cmailer.net",
  "portal_url": "https://portal.cmailer.net",
  "max_recipients": 50,
  "max_attachments": 20,
  "max_message_bytes": 10485760,
  "max_batch": 100,
  "max_tags": 10,
  "max_custom_headers": 20,
  "max_subject_length": 998,
  "rate_limit_per_minute": 120,
  "retention_days": 90
}

Rate limits#

Each key can make 120 requests a minute to /v1, counted over a sliding 60-second window. Reads count too, a batch costs one per email, and a send that fails validation still counts. The management API isn’t part of this limit.

Every request that counts gets these headers:

Rate limit headers
HeaderMeaning
X-RateLimit-LimitRequests allowed per minute.
X-RateLimit-RemainingRequests left in the current window.
X-RateLimit-ResetWhen the oldest counted request leaves the window, in Unix seconds.
Retry-AfterOn 429 only: seconds to wait before retrying.

When the limit is reached the response is 429 rate_limit_exceeded and nothing is sent. Wait for the Retry-After seconds before trying again.

Headers · 429
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1790154855
Response · 429
{
  "statusCode": 429,
  "name": "rate_limit_exceeded",
  "message": "Rate limit of 120 messages per minute exceeded. Retry after 12s."
}

Management API#

Domain keys with full access or management-only access can also manage mailboxes and forwards on their domains, with the same bearer header and base URL. Inbox keys and sending-only keys get 403.

These endpoints predate /v1 and answer in the portal’s own format: successful responses vary by endpoint (below), and errors carry statusCode and statusMessage (the reason in words) rather than the email API’s name codes.

Management API endpoints
EndpointWhat it does
GET /api/external/domainsLists the domains the key covers. Returns { data }.
GET /api/external/mailboxes?domain=Lists mailboxes with their name, quota and state; domain is optional. Returns { data }.
POST /api/external/mailboxesCreates a mailbox from { email, password, name?, quota? }. Returns { success, data }.
POST /api/external/mailboxes/reset-passwordSets a new password from { email, password }. Returns { success, message }.
GET /api/external/forwardings?domain=Lists forwards and catch-alls; domain is optional. Returns { domains, count, data }.
POST /api/external/forwardingsCreates a forward from { domain, address, forwarding, active? }. Use @domain as the address for a catch-all. Returns { success, data }.
PUT /api/external/forwardings/{id}Changes a forward’s destination or turns it on or off, from { forwarding, active? }. Send forwarding every time. Returns { success, data }.
DELETE /api/external/forwardings/{id}Removes a forward. Returns { success, message }.

Mailbox passwords must be 8 to 128 characters and include an uppercase letter, a lowercase letter, a digit and a special character. quota is in MB. Left out, it’s the server’s default, capped at the domain’s own limit; a quota over that limit is rejected, and so is 0 (no limit) on a domain that has one.

Older keys starting ext_ can only list domains and forwards and turn a forward on or off.

Create a mailbox
Request
curl https://api.cmailer.net/api/external/mailboxes \
  -H "Authorization: Bearer $CMAILER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jane@acme.example",
    "password": "Replace-with-a-strong-passw0rd",
    "name": "Jane Doe",
    "quota": 2048
  }'
Response · 200
{
  "success": true,
  "data": {
    "username": "jane@acme.example",
    "domain": "acme.example",
    "name": "Jane Doe",
    "quota": 2048
  }
}

Good to know#

  • Signing and alignment. Mail is DKIM-signed on the way out. A hosted domain with its own DKIM key is signed as itself; other domains, including every send-only domain, are signed as cmailer.net, so their DMARC passes on SPF alignment. The records cMailer set up for a domain already cover mail sent through the API.
  • Bounces go to the from address, as with mail sent from a mailbox, so send from a real address someone reads.
  • No scheduling or templates. Send each message when it’s due and render its content in your application. scheduled_at is rejected.
  • No webhooks. Poll GET /v1/emails/{id} for outcomes; answers are reused for a minute, so there’s no point polling faster.
  • Privacy. The send log keeps addresses, subject, size, attachment count, tags and outcome for 90 days. Message bodies and attachments are never stored.