smtp2web is a TLS-enabled SMTP ingestion service. It accepts email over
SMTP, parses each message into structured JSON, queues it durably on disk, and
forwards it to an HTTP or HTTPS endpoint. XML can be generated at the forwarding
edge when a receiver requires it.
The repository is laid out as a root-relative Linux filesystem tree plus
install.sh, which installs the files into their production locations.
/etc/smtp2web/config.json
/etc/smtp2web/certs/
/opt/smtp2web/
/usr/local/bin/zip-smtp2web-archives.sh
/var/lib/smtp2web/spool/
/var/lib/smtp2web/spool/failed/
/var/lib/smtp2web/spool/quarantine/
/var/lib/smtp2web/archive/
/var/log/smtp2web/
Systemd units are installed under /etc/systemd/system:
smtp2web.service
zip-smtp2web-archives.service
zip-smtp2web-archives.timer
- Linux with systemd
- Root privileges for installation
- Node.js 20.19.0 or newer
- npm
- OpenSSL
zipandflockfor archive compression- A firewall that can restrict the SMTP listener to trusted sources
git clone https://github.com/i81b4u/smtp2web.git
cd smtp2web
sudo ./install.sh
sudo su -s /bin/bash smtp2web -c 'cd /opt/smtp2web && npm ci --omit=dev'
sudo editor /etc/smtp2web/config.json
sudo systemctl daemon-reload
sudo systemctl enable --now smtp2web.service
sudo systemctl enable --now zip-smtp2web-archives.timerSee INSTALL.md for detailed installation, update, firewall, and verification steps.
The default configuration uses:
/etc/smtp2web/certs/private.pem
/etc/smtp2web/certs/public.pem
/etc/smtp2web/certs/rootca.pem
If the configured key or certificate is missing or empty, certs.js generates a
self-signed SMTP certificate at startup. Production deployments should replace
the generated certificate with material from the organisation's CA. The private
key is installed as root:smtp2web with mode 0440, so it is readable by the
service group and not world-readable.
Incoming messages are written to /var/lib/smtp2web/spool before forwarding.
The queue file is the source of truth until HTTP delivery succeeds and, when
enabled, archiving succeeds.
- Invalid JSON or structurally invalid spool files are moved to
spool/quarantinewithout consuming delivery retries. - Repeated delivery failures are moved to
spool/failedafterqueue.maxAttempts. - Moving a failed JSON file back into the active spool is treated as a manual
replay. Retry metadata is reset, while a preserved
forwardedAtmarker prevents duplicate HTTP delivery after an archive-only failure.
By default, smtp2web provides at-least-once delivery: any HTTP 2xx response,
including 200 and 202, is treated as receiver acceptance. A network failure
after a receiver processes a request can therefore result in a retry and a
duplicate delivery.
Receivers that support deduplication can opt in by setting
forwarder.idempotency.enabled to true. smtp2web then sends the stable queue
UUID in the Idempotency-Key header (or the configured
forwarder.idempotency.header). The receiver must record that value and return
a 2xx response for repeats without processing them again. This setting is off
by default, so existing receivers receive the same requests as before.
smtp2web validates its own versioned payload contract before forwarding. It
requires durable metadata, a parsed mail object, and TLS session metadata, but
deliberately does not require or RFC-validate optional mail headers such as
From, To, or Subject. That keeps legitimate device-generated mail
compatible while still quarantining corrupt or manually malformed spool files.
New messages use schema version 1. Queue files created by earlier releases, which have no schema version marker, remain valid for replay.
Delivered messages are archived by date under:
/var/lib/smtp2web/archive/YYYY-MM-DD/
Archive files are written atomically, so the compression timer only ever sees complete JSON payloads.
/usr/local/bin/zip-smtp2web-archives.sh compresses archived JSON files into
date-local zip files and removes archive directories older than its retention
window. The installed systemd timer runs this daily.
Application logs are newline-delimited JSON in:
/var/log/smtp2web/smtp2web.log
Log rotation is configured by /etc/logrotate.d/smtp2web.
This repository was developed with assistance from OpenAI Codex for coding, review, and troubleshooting.
Run the structural validation tests from the deployed application directory:
npm testThe integration suite is a black-box test of the SMTP and HTTP boundaries. It
starts an isolated smtp2web process with temporary TLS certificates, spool,
archive, log paths, and loopback ports. It uses swaks as an SMTP client and a
bundled Python HTTP listener as a controllable API gateway; it does not need
root, systemd, or a running production installation, and does not touch the
configured service or its data.
Prerequisites are Node.js dependencies (npm ci), Python 3, swaks, curl,
and OpenSSL. Run it from /opt/smtp2web or this repository's application
directory:
npm run test:integrationThe runner reports these scenarios as it executes them:
- STARTTLS mail forwarded after HTTP
200. - Plain SMTP rejected when TLS is required.
- HTTP
202accepted as asynchronous receiver acceptance. - HTTP
500retained and retried until a later200. - Corrupt spool JSON moved directly to quarantine.
- Opt-in idempotency header matches the stable message UUID.
Temporary files are removed at the end of a run. To retain the generated configuration, certificates, logs, and spool for troubleshooting, run:
SMTP2WEB_TEST_KEEP=1 npm run test:integration