NodeMailServer
Guides

JSON send API

Send mail with one HTTP request. The server builds the MIME message and sends it through the same pipeline as port 587.

The JSON send API lets your own apps send mail without an SMTP library. You post JSON, the server builds the MIME message in a worker thread, and the message then goes through the same steps as mail submitted on port 587.

Turn it on

The API is off until you set a token.

SettingWhereDefault
MAIL_API_TOKENserver/.envnone. The API does not start without it.
MAIL_API_HOSTserver/.env0.0.0.0
MAIL_API_PORTserver/.env8080
api.enabledserver/config/mail.config.jsontrue

Keep it private

The token is the only protection. Put the API behind a TLS reverse proxy, or listen on a private network, and do not open port 8080 to the internet.

Send a message

curl -X POST http://localhost:8080/v1/messages \
  -H "Authorization: Bearer $MAIL_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "from": { "name": "Me", "address": "me@yourdomain.com" },
    "to": "user@example.com",
    "cc": ["Team <team@example.com>"],
    "bcc": "audit@yourdomain.com",
    "replyTo": "support@yourdomain.com",
    "subject": "Hello",
    "text": "Plain text body",
    "html": "<p>HTML body</p>",
    "headers": { "X-Order-Id": "1234" },
    "attachments": [
      { "filename": "invoice.pdf", "content": "<base64>", "contentType": "application/pdf" }
    ]
  }'

A successful call answers 202 Accepted:

{ "id": "3f2c9a52-1b0e-4c64-9f6c-0d3f0f0c9a11", "from": "me@yourdomain.com", "recipients": ["user@example.com", "team@example.com", "audit@yourdomain.com"] }

202 means the message is in the outbound queue, not that it was delivered. Delivery results and bounces are handled by the bounce server.

Fields

FieldRequiredDescription
fromyesExactly one address.
to, cc, bccat least one of themOne address or a list.
replyTonoOne address or a list.
subjectnoA single line.
text, htmlat least one of themThe message body.
headersnoAn object of extra headers. Values are single-line text.
inReplyTo, referencesnoThreading headers.
attachmentsnoA list of { filename, content, contentType, cid }. content is base64. Set cid to use the file inline from the HTML.

An address can be "a@b.com", "Name <a@b.com>", { "name": "Name", "address": "a@b.com" }, or a list of these.

Rules

  • bcc addresses receive the message but never appear in its headers.
  • Duplicate addresses are sent once.
  • The number of recipients is limited by outgoing.maxRecipients, and the message size by outgoing.maxMessageSizeBytes.
  • headers cannot set From, To, Cc, Bcc, Subject, Content-Type, Content-Transfer-Encoding, MIME-Version, DKIM-Signature or X-AE-Queue-Id.
  • Outgoing headers from mail.config.json are added after yours and replace a header with the same name.

Errors

Every error is JSON: { "error": "<message>" }.

StatusMeaning
400The body is not valid. The message says which field.
401The token is missing or wrong.
413The message is larger than outgoing.maxMessageSizeBytes.
429The client is over the outgoing rate limit.
503The worker queue is full. Try again later.
500The message could not be queued. The cause is in the server log.

Why the server builds the MIME message

KumoMTA's own JSON form has no cc field, and SMTP needs a finished message. Building the MIME message here, then sending it unchanged, means every outbound driver receives the same raw message and the same recipient list, including Bcc.

Components involved

ComponentFilePurpose
ApiServerConfigserver/src/server/ApiServer.tsExpress server, Bearer token check, body size limit.
JsonMailHandlerserver/src/services/JsonMailHandler.tsValidates the JSON and builds the task.
composeMimeserver/src/lib/workers/mailTasks.tsBuilds the MIME message with nodemailer in a worker.
submitOutgoingserver/src/services/OutgoingPipeline.tsAdds headers, signs with DKIM and queues. Shared with port 587.

On this page