NodeMailServer
Reference

Configuration files

What lives in .env, mail.config.json and smtp-messages.json, and every switch in mail.config.json.

Settings are split three ways:

WhereWhat
server/.envHosts, ports, keys, passwords and the driver choice. See Environment variables.
server/config/mail.config.jsonWhat is turned on or off, policy actions, limits, queue and bounce behaviour.
server/config/smtp-messages.jsonThe code, enhanced status and text of every SMTP reply the server sends.

Only server/src/config/env.ts reads environment variables. Fixed values, such as driver names, DSN statuses, default settings and the built-in reply catalog, are in server/src/config/constants.ts.

Move the files with MAIL_CONFIG_PATH and SMTP_MESSAGES_PATH. If a file is missing, the server logs a notice and uses the built-in defaults. A key you leave out of a file also uses its default, so a file only needs the settings you change.

Reloading

Both JSON files reload when you save them. You do not restart the server.

  • If a file has a mistake, the server logs each problem and keeps the previous settings.
  • Unknown keys, values of the wrong type, bad actions, bad CIDR ranges and bad header names are all reported.
  • Listener settings apply on restart: turning the bounce servers on or off, and the size advertised in EHLO. Everything else applies straight away.

mail.config.json

Policy actions

Each check in incoming.checks takes one action:

ActionResult
acceptIgnore the result.
tagAccept the message, and add X-AE-Spam-Flag: YES and X-AE-Policy-Flags to it.
tempfailReply 4xx, so the sender retries later.
rejectReply 5xx.

A DNS error never causes a hard reject. reject becomes tempfail for that message.

Incoming checks

CheckSettingDefaultReject reply
Reverse DNS, with forward confirmationreverseDns.enabled, requireForwardConfirmed, onFailoff, true, tag550 5.7.25
Disposable sender addressdisposableSender.enabledon550 5.1.7
Sender domain has no MX, A or AAAA recordsenderDomain.enabled, onUnroutableon, reject550 5.1.8
SPF fail, softfail, none, temperrorspf.onFail, onSoftFail, onNone, onTempErrortag, accept, accept, accept550 5.7.23
DKIM fails, or no signaturedkim.onFail, onNonetag, accept550 5.7.20
DMARC fails with p=reject or p=quarantinedmarc.onReject, onQuarantine, honorPercentagereject, tag, true550 5.7.26

The reverse DNS check runs when the client connects. The sender checks run at MAIL FROM, and the SPF, DKIM and DMARC checks run after DATA. A bounce (MAIL FROM:<>) skips the sender domain check.

Limits

SettingDefaultDescription
incoming.maxMessageSizeBytes10485760Largest message accepted on port 25.
incoming.maxRecipients100Recipients for each message on port 25.
incoming.rateLimiton, 100enabled and maxMessagesPerMinute. Going over the limit is a strike against the IP.
incoming.ipBlocking.enabledtrueKeep strikes in SQLite and block an IP for good after the last temporary block. Off: short in-memory blocks only.
incoming.ipBlocking.temporaryBlockMinutes15, 60One entry per temporary block. The strike after the last entry is permanent. [] blocks for good at once.
incoming.ipBlocking.strikeResetHours24An IP with no new strike for this long starts again from strike 1.
incoming.trustedNetworksloopbackCIDR ranges that skip the checks and the rate limit, for example an internal relay.
incoming.recipients.acceptedDomainsemptyDomains you accept mail for. Empty accepts any domain.
incoming.recipients.knownRecipientsemptyAddresses you accept. Empty accepts any user.
outgoing.maxMessageSizeBytes10485760Largest message accepted on port 587 and by the JSON API.
outgoing.maxRecipients100Recipients for each message.
outgoing.rateLimiton, nullenabled and maxConnectionsPerMinute. null uses MAX_EMAILS_PER_MINUTE.

Pipeline switches

SettingDefaultDescription
incoming.arcSealtrueSeal incoming mail with ARC. Also needs ARC_PRIVATE_KEY_PATH and ARC_SIGNING_DOMAIN.
incoming.pgpEncryptiontrueEncrypt incoming mail for storage. Also needs the PGP_* keys.
incoming.customHeadersonenabled and headers. Extra headers added to incoming mail after the checks.
outgoing.dkimSigntrueSign outgoing mail with DKIM. Also needs DKIM_PRIVATE_KEY.
outgoing.bounceHeaderon, X-AE-Bounce-Addressenabled and name. Adds the bounce address when BOUNCE_DOMAIN is set.
outgoing.imapOutbox.enabledtruePut mail sent on port 587 in the sender's Outbox, then move it to Sent. Also needs IMAP_LMTP_HOST. See Outbox and Sent.
outgoing.customHeadersoffenabled and headers. Extra headers added to outgoing mail.
api.enabledtrueThe JSON send API. Also needs MAIL_API_TOKEN.

Values in customHeaders.headers can use {queueId} and {sender}. See Outgoing headers.

Queue and bounces

SettingDefaultDescription
queue.maxConcurrent10Deliveries at the same time.
queue.maxAgeHours48Give up on a message after this long.
queue.retryDelaysMinutes1, 5, 15, 30, 60, 120, 240Wait before each retry. The last value repeats.
bounces.notifySendertrueSend the original sender a bounce report.
bounces.suppression.enabledtrueKeep a list of addresses that hard-bounced.
bounces.suppression.days30How long an address stays on the list.
bounces.trackingDays7How long a sent message is remembered, so a late bounce can be matched.
bounces.smtpServer.enabledtrueThe bounce SMTP server.
bounces.httpServer.enabledtrueThe bounce HTTP server.

See Bounces.

smtp-messages.json

Every reply the server sends is listed here by key:

"MAILBOX_FULL": { "code": 552, "status": "5.2.2", "message": "Mailbox {recipient} is full" }

Change the text or the code and save. {placeholders} are filled in when the reply is sent. The status must match the class of the code, so 5xx goes with 5.x.x.

KeyReply
GREETING220 2.0.0 The banner.
BOUNCE_GREETING220 2.0.0 The banner of the bounce SMTP server.
MESSAGE_ACCEPTED250 2.0.0 Mail accepted on port 25.
MESSAGE_QUEUED250 2.0.0 Mail queued on port 587. {id} is the queue id.
BOUNCE_ACCEPTED250 2.0.0 A bounce was accepted.
SERVICE_UNAVAILABLE421 4.3.2
RATE_LIMITED421 4.7.0 {ip}, {seconds}
MAILBOX_BUSY450 4.2.1 {recipient}
REVERSE_DNS_TEMPFAIL450 4.7.25 {ip}
INTERNAL_ERROR451 4.3.0
SERVER_BUSY451 4.3.2 The worker queue is full.
DNS_TEMPORARY_FAILURE451 4.4.3 {domain}
SENDER_DOMAIN_TEMPFAIL451 4.1.8 {domain}
SPF_TEMPFAIL, DKIM_TEMPFAIL, DMARC_TEMPFAIL451 4.7.24, 451 4.7.20, 451 4.7.26
INSUFFICIENT_STORAGE452 4.3.1
MAILBOX_FULL_TEMPORARY452 4.2.2 {recipient}
TOO_MANY_RECIPIENTS452 4.5.3 {max}
AUTH_METHOD_NOT_ALLOWED504 5.5.4 XOAUTH2 is rejected.
AUTH_FAILED535 5.7.8
USER_NOT_FOUND550 5.1.1 {recipient}
UNKNOWN_BOUNCE_ADDRESS550 5.1.1 {recipient}
INVALID_SENDER, DISPOSABLE_SENDER550 5.1.7
SENDER_DOMAIN_UNROUTABLE550 5.1.8 {domain}
MAILBOX_DISABLED550 5.2.1 {recipient}
RELAY_DENIED550 5.7.1 {recipient}
DKIM_FAILED550 5.7.20
SPF_FAILED550 5.7.23 {domain}
REVERSE_DNS_FAILED550 5.7.25 {ip}
DMARC_REJECT550 5.7.26 {domain}
MAILBOX_FULL552 5.2.2 {recipient}
MESSAGE_TOO_LARGE552 5.3.4 {maxSize}
NO_VALID_RECIPIENTS554 5.5.1
IP_BLOCKED554 5.7.1 {ip} The IP is blocked for good.

Not every reply is here

Replies that smtp-server sends itself, such as command syntax errors or 530 Authentication required, are not in this file.

On this page