Configuration
Config file path defaults to /config/config.yaml. Override with CONFIG_PATH. Use ${VAR} / ${VAR:-default} in config and in template.yaml. Unset required ${VAR} values fail startup (no empty secrets).
Special env names: CONFIG_PATH, TEMPLATES_DIR, PORT (default 3000), LOG_LEVEL, NODE_ENV. Everything else is whatever your YAML references.
You must define auth, and at least one of email.accounts or sms.accounts.
Auth: API key
auth:
type: apiKey
header: x-notifier-api-key # optional; this is the default
value: ${NOTIFIER_API_KEY}Send that header on /email/send and /sms/send. /health and /ready need no auth.
Auth: HMAC
auth:
type: hmac
header: x-notifier-signature # optional; this is the default
secret: ${NOTIFIER_SIGNING_SECRET}
tolerance: 300 # secondsHeader value: t=<unix-seconds>,v1=<hmac-sha512-hex>.
Message: t + "." + exact raw JSON body. Millisecond timestamps are rejected. Outside tolerance → 401.
import crypto from 'node:crypto';
export function sign(body, secret) {
const t = Math.floor(Date.now() / 1000);
const v1 = crypto
.createHmac('sha512', secret)
.update(`${t}.`)
.update(body)
.digest('hex');
return `t=${t},v1=${v1}`;
}Email accounts
email:
accounts:
zeptomail:
type: zeptomail
from: ${MAIL_FROM_EMAIL}
apiKey: ${ZEPTOMAIL_API_KEY}
# optional: fromName, bounceAddress
ses:
type: ses
from: ${MAIL_FROM_EMAIL}
region: ${AWS_REGION}
accessKeyId: ${AWS_ACCESS_KEY_ID}
secretAccessKey: ${AWS_SECRET_ACCESS_KEY}
defaults:
account: zeptomail
renderer: react-emailPick an account per request (account), per template (account in template.yaml), or via email.defaults.account. Field merge order for from/subject/etc.: account → template → request (request wins).
email.defaults.renderer is the fallback when a template omits renderer. If email accounts exist and any template fails to load, the process refuses to start.
SMS accounts
sms:
accounts:
termii:
type: termii
apiKey: ${TERMII_API_KEY}
from: ${TERMII_FROM}
version: v3 # v3 | v4
channel: dnd # dnd | generic
messageType: plain # plain | unicode
# optional: baseUrl (fixed host; then sendOptions.version is rejected)
defaults:
account: termiiSMS has no templates. Body text comes on the request.
Request limits
requestValidation:
maxBodySize: 1048576
maxAttachmentSize: 10485760
maxAttachments: 10
allowedAttachmentMimeTypes:
- application/pdf
- image/png
- image/jpegOmit the block to use defaults (1 MiB body, 10 MiB per attachment, 10 attachments, common MIME allowlist).
Private network
- Place notifier behind internal load balancing / mesh. Do not expose send routes publicly.
- Terminate TLS or mTLS at the edge.
- Prefer HMAC when callers are other services (integrity + replay window).
/healthand/readyare open for probes; they are not auth for sends.