Skip to content

nogringo/haraka-webhook

Repository files navigation

Haraka Webhook Receiver

Reusable inbound SMTP receiver powered by Haraka. It accepts mail, stores each message in a durable local spool, then POSTs a Mailgun-like /mime payload to a configurable webhook.

What It Does

  • Listens for inbound SMTP delivery on 25/TCP.
  • Accepts every recipient by default.
  • Optionally restricts accepted recipient domains with environment variables.
  • Stores raw RFC822/MIME messages in a persistent spool before acknowledging SMTP.
  • Retries webhook delivery in the background.
  • Sends application/x-www-form-urlencoded payloads with body-mime.
  • Supports opportunistic inbound STARTTLS when certificate files are mounted.

Quick Start

cp .env.example .env
docker compose up -d

To build the image locally from this repository instead:

docker compose -f docker-compose.build.yml up --build -d

Edit .env before production use:

WEBHOOK_URL=https://webhook.example.com/inbound
WEBHOOK_SIGNING_KEY=change-me
ACCEPT_ALL_RECIPIENTS=true

Optional pre-webhook decision API:

WEBHOOK_DECISION_URL=https://policy.example.com/haraka/decision
WEBHOOK_DECISION_TOKEN=change-me
WEBHOOK_DECISION_PAYLOAD_MODE=minimal

Webhook Payload

The receiver POSTs application/x-www-form-urlencoded fields similar to Mailgun's raw MIME route:

  • recipient
  • sender
  • from
  • subject
  • message-headers
  • timestamp
  • token
  • signature, when WEBHOOK_SIGNING_KEY is set
  • body-mime

The signature is an HMAC-SHA256 hex digest of timestamp + token, keyed by WEBHOOK_SIGNING_KEY.

Recipient Policy

Default behavior accepts every RCPT TO delivered to this server:

ACCEPT_ALL_RECIPIENTS=true

To restrict by domain:

ACCEPT_ALL_RECIPIENTS=false
ACCEPTED_DOMAINS=example.com,example.net

Decision API

When WEBHOOK_DECISION_URL is set, each message is checked before it is accepted into the spool. The decision API can return allow, deny, or silent_deny. Failures are treated as temporary SMTP failures so the sender can retry later.

WEBHOOK_DECISION_PAYLOAD_MODE controls how much message data is sent to that API: minimal, summary, or full. See DecisionProtocol.md. When WEBHOOK_DECISION_TOKEN is set, the decision request includes Authorization: Bearer <token>.

Role Address Routing

When WEBHOOK_ROLE_URL is set, mail addressed to a role local-part is delivered to that second webhook instead of WEBHOOK_URL:

WEBHOOK_ROLE_URL=https://webhook.example.com/role-inbound

If any recipient's local-part (the part before @, case-insensitive) is a role address, the whole message is POSTed to the role webhook; otherwise it goes to WEBHOOK_URL. The payload, signing key (WEBHOOK_SIGNING_KEY), and timeout are identical for both webhooks — only the URL differs. When WEBHOOK_ROLE_URL is unset, all mail goes to WEBHOOK_URL (unchanged behavior).

The default role list is:

abuse, admin, administrator, billing, contact, daemon, help, hostmaster, info,
legal, mail, mailerdaemon, marketing, noc, noreply, nostr, postmaster, privacy,
root, sales, security, spam, sysadmin, support, usenet, uucp, webmaster, www

Override it with ROLE_ADDRESSES (comma-separated). Setting this replaces the default list rather than extending it:

ROLE_ADDRESSES=abuse,admin,postmaster,support

TLS And Certificates

Haraka uses mounted certificate files for SMTP STARTTLS. Use certbot, acme.sh, lego, your DNS provider, or your hosting platform to obtain and renew certs.

Mount the certificate files read-only in docker-compose.yml, then set:

SMTP_TLS_CERT_PATH=/certs/fullchain.pem
SMTP_TLS_KEY_PATH=/certs/privkey.pem

STARTTLS is opportunistic by default, which keeps delivery compatible with standard MX traffic.

DNS And Ports

For each domain that should deliver mail here, create MX records pointing to the host running this container. That host also needs an A and/or AAAA record and public inbound 25/TCP.

Example:

example.com.           MX 10 mail.example.com.
example.net.           MX 10 mail.example.com.
mail.example.com.      A  203.0.113.10

Spool And Retries

Spool directories live under SPOOL_DIR:

  • pending/: messages waiting for webhook delivery
  • processing/: messages currently being delivered
  • dead/: permanent failures, including webhook 406
  • tmp/: temporary writes before atomic move into pending/

Useful settings:

SPOOL_DIR=/var/spool/haraka-webhook
WEBHOOK_TIMEOUT_MS=10000
RETRY_SCAN_INTERVAL_MS=5000
RETRY_INTERVAL_MS=30000
RETRY_MAX_INTERVAL_MS=3600000
MAX_RETRY_AGE_MS=0

MAX_RETRY_AGE_MS=0 means retry forever, except 406, which is dead-lettered.

For disk-level secrecy, run the spool volume on encrypted storage.

Local Tests

npm test

After installing dependencies, you can run Haraka locally:

WEBHOOK_URL=https://example.com/hook npm start

Use swaks to send a test message:

swaks -s 127.0.0.1 -p 25 -f sender@example.com -t user@example.com

License

MIT

About

Reusable Haraka SMTP receiver that spools inbound mail and forwards it to a Mailgun-style MIME webhook.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors