High-performance Node.js email microservice with split core/web architecture.
GPLv3 community release: this project is licensed under the GNU General Public License v3.0. The goal is to provide an open, inspectable mail queueing, caching, delivery, and operations platform that the community can run, study, fork, and improve.
Core design:
- Incoming email requests are validated, checked for idempotency/suppression, and written to Redis queue immediately.
- Worker processes consume leased Redis jobs and deliver via pooled SMTP accounts.
- System logs are persisted to SQLite and file logs simultaneously.
- Monitor web panel is served by a separate web service.
- SMTP credentials and web admin password verifier are stored in an encrypted SQLite vault.
Client -> Core API (/send, /auth/token, /health) -> Queue -> Worker -> SMTP Provider
\-> Structured Logger -> SQLite + File + Console
Web Panel Service (:8080 root) -> Core Monitor APIs (/monitor/stats, /monitor/stream, /metrics)
-> SMTP Account Manager + Secure Update Control (scripts/updater.js)
The legacy web panel is served directly from the root of port 8080.
Screenshots below were captured from a local development run on 2026-05-27.
- Fast ACK pattern (
202 queued) without waiting SMTP round-trip - Redis-backed mail queue (
QUEUE_BACKEND=redis) with processing leases, visibility timeout, and ack - API/worker role split with
MAILFASTAPI_ROLE=api|worker|all - Production safety guard with
PRODUCTION_MODE=true - Idempotency records, lifecycle states, suppression list, delivery events, dead-letter jobs, and hash-chained audit events in operational SQLite
- Domain/account delivery policies for Gmail, Outlook, Yahoo, and corporate mail systems
- Bounce classifier plus protected hard-bounce and complaint webhook ingestion
- One-click unsubscribe support for marketing/bulk mail
- Dedicated Return-Path generation and optional DKIM signing
- SPF/DKIM/DMARC/MX/MTA-STS/TLS-RPT domain health diagnostics on the monitor API
- Cached Nodemailer pooled transporters per SMTP account
- Encrypted SMTP account vault (
data/mailfastapi-secure.sqlite) protected bySECURE_STORE_KEY - First-run web panel password setup, optional TOTP MFA, session cookies, CSRF protection, and login lockout
- Optional per-mail
smtpAccount/from, multi-recipientto, and base64 attachments - Worker retry logic and latency metrics (
queueLatencyMs,dispatchLatencyMs) - JWT auth (
/auth/token+ Bearer on/send) and rate limiting - Dual log persistence:
- SQLite (
LOG_DB_PATH) - JSON line file (
LOG_DIR/LOG_FILE_NAME)
- SQLite (
- CLI log dashboard:
npm run log mailsendernpm run log:mailsender
- Legacy web monitor at
http://localhost:8080with SMTP account filtering - SMTP account management at
http://localhost:8080/smtp - Encrypted application settings management at
http://localhost:8080/settings
MailFastApi now includes the core repository controls required for a larger transactional and marketing email platform:
| Capability | Current status |
|---|---|
| Multi SMTP accounts | Encrypted SQLite vault, account selection by smtpAccount or sender address |
| Split API/worker runtime | `MAILFASTAPI_ROLE=api |
| Durable production queue | Redis backend required by production guard |
| Local/dev queue | Memory queue remains available for development and isolated tests |
| Idempotency | Tenant/actor scoped idempotency records prevent duplicate queue writes |
| Retry and DLQ | Worker retry, lifecycle states, and dead-letter persistence |
| Failed-mail replay | DLQ listing and authenticated retry back into the queue |
| Automatic failed retry | Worker scans pending DLQ records and requeues them up to DLQ_AUTO_RETRY_MAX_ATTEMPTS |
| Suppression | Global and tenant-level suppression for bounces, complaints, and unsubscribes |
| Deliverability | SPF/DKIM/DMARC/MX/MTA-STS/TLS-RPT diagnostics and optional DKIM signing |
| Security | JWT/API-key auth, production guard, CSRF, CSP nonce, session hardening, RBAC |
| Application settings | Encrypted secure-store overrides for runtime, queue, auth, web, monitor, delivery, DKIM, logging, and updater settings |
| MFA | Local TOTP can be enabled; development .env can disable it while production guard enforces safer settings |
| Updates | Node.js updater with fast-forward flow, rollback, progress reporting, and signed-tag mode |
| Observability | Health, Prometheus metrics, delivery events, queue depth, worker state, and legacy monitor |
External production controls are still required for a real enterprise deployment: private network or VPN access for the admin panel, TLS termination, KMS/Vault or OS secret delivery, owned DNS records, provider feedback-loop enrollment, signed release keys, Prometheus/Grafana deployment, and production-grade Redis persistence/failover.
mailFastApi/
|-- src/
| |-- app.js
| |-- web.js
| |-- auth.js
| |-- mailQueueFactory.js
| |-- memoryMailQueue.js
| |-- redisMailQueue.js
| |-- mailer.js
| |-- operationalStore.js
| |-- productionGuard.js
| |-- domainHealth.js
| |-- deliveryPolicy.js
| |-- bounceClassifier.js
| |-- dkimConfig.js
| |-- secureStore.js
| |-- webAuth.js
| |-- queue.js
| |-- worker.js
| |-- systemLogger.js
| `-- systemStore.js
|-- scripts/
| |-- log-cli.js
| `-- updater.js
|-- docs/
| |-- API_DOCS.md
| |-- ENTERPRISE_HARDENING.md
| |-- TEST_REPORT.md
| `-- assets/
|-- Tests/
|-- updater.sh
|-- .env.example
`-- package.json
See .env.example for full reference.
Important variables:
- Queue:
QUEUE_BACKEND=redisREDIS_URL=redis://127.0.0.1:6379REDIS_QUEUE_KEY=mailfastapi:mail_jobsQUEUE_VISIBILITY_TIMEOUT_MS=300000QUEUE_RECLAIM_INTERVAL_MS=30000
- Runtime role:
PRODUCTION_MODE=false|trueMAILFASTAPI_ROLE=all|api|worker
- Logs:
LOG_DB_PATH=data/mailfastapi.sqliteOPERATIONAL_DB_PATH=data/mailfastapi-operational.sqliteLOG_DIR=logsLOG_FILE_NAME=system.log
- Secure store:
SECURE_STORE_KEYencrypts/decryptsdata/mailfastapi-secure.sqliteSECURE_STORE_KEY_FILEcan point to a mounted secret file instead of storing the key in.env- SMTP host/user/password/from values are managed from the web panel, not
.env
- Send payload controls:
REQUEST_BODY_LIMIT(default10mb)MAX_ATTACHMENTS(default10)MAX_ATTACHMENT_TOTAL_BYTES(default8388608)
- Live monitor:
- Core service:
MONITOR_ENABLED,MONITOR_UI_ENABLED,MONITOR_PATH,METRICS_PATH MONITOR_SSE_INTERVAL_MS,MONITOR_TOKENMONITOR_MAX_RECENT_ENTRIES,MONITOR_MAX_TIMELINE_MINUTES
- Core service:
- Web service:
- fixed legacy panel port:
8080 WEB_HOST,WEB_CORE_BASE_URLWEB_MFA_REQUIRED=falsefor development,trueis required in production modeWEB_SESSION_IDLE_TIMEOUT_MS,WEB_SESSION_ABSOLUTE_TIMEOUT_MSWEB_ENABLE_UPDATER,WEB_UPDATE_SCRIPT,WEB_UPDATE_TIMEOUT_MSWEB_UPDATE_TOKEN(optionalx-update-tokenheader protection for update endpoints)
- fixed legacy panel port:
- Updater:
UPDATER_RELEASE_MODE=branch|tagUPDATER_ALLOWED_TAG_PATTERNandUPDATER_REQUIRE_SIGNED_TAGUPDATER_NPM_BIN,UPDATER_RUN_TESTS,UPDATER_HEALTH_TIMEOUT_MS,UPDATER_LOCK_STALE_MS
- Delivery safety:
IDEMPOTENCY_ENABLED,IDEMPOTENCY_HEADER,IDEMPOTENCY_TTL_MSSUPPRESSION_ENABLED,SUPPRESSION_APPLIES_TOPUBLIC_BASE_URL,UNSUBSCRIBE_SECRETBOUNCE_WEBHOOK_ENABLED,BOUNCE_WEBHOOK_TOKEN,BOUNCE_DOMAINDELIVERY_POLICY_ENABLED,DOMAIN_POLICIES_JSON,SMTP_ACCOUNT_POLICIES_JSONDKIM_SIGNING_ENABLED,DKIM_DOMAIN,DKIM_SELECTOR,DKIM_PRIVATE_KEY_PATHDOMAIN_HEALTH_DKIM_SELECTORS
Requires Node.js >=22.5.0 because the encrypted vault uses the built-in node:sqlite module.
npm install
npm start core
npm start webCore URL (default): http://localhost:3000
Web panel URL (fixed): http://localhost:8080
Start targets:
npm startornpm start core: core service using the configuredMAILFASTAPI_ROLEnpm start web: legacy web panel on fixed port8080npm start api: core service withMAILFASTAPI_ROLE=apinpm start worker: worker process withMAILFASTAPI_ROLE=workernpm start all: core service with API and worker roles
On first web panel access, create the admin password. Then open SMTP Accounts and add accounts such as 2fa, info@example.com, or Bilgi Maili. Runtime mail delivery reads these accounts from the encrypted SQLite vault.
Prometheus metrics proxy URL (requires web login): http://localhost:8080/metrics
Encrypted settings URL (requires admin login): http://localhost:8080/settings
Settings saved from this page are stored in the encrypted SQLite secure store and applied when the relevant core or web process starts. Secret values are write-only in the browser; existing secret values are shown only as masked status.
Formatted monitor pages:
- Metrics view:
http://localhost:8080/metrics-view - Raw snapshot view:
http://localhost:8080/raw-view
Project root includes a cross-platform installer entrypoint:
installer.shdetects Linux, macOS, or Windows Git Bash/MSYS/Cygwin and dispatches to the correct platform installer.install.shis the Linux systemd installer.macosinstaller.shis the macOS launchd installer.install.ps1andinstaller.cmdare native Windows installers.
Linux registers two systemd services:
mailfastapi-core.servicemailfastapi-web.service
macOS registers two LaunchAgents:
com.mailfastapi.corecom.mailfastapi.web
Windows registers two Scheduled Tasks:
mailfastapi-coremailfastapi-web
All installers:
- creates/updates
.envfrom.env.example - generates
SECURE_STORE_KEYandMONITOR_TOKENif missing/default - appends missing core/web settings
- checks core/web ports
- creates runtime directories and permissions
- installs npm dependencies
- runs Node syntax checks
- enables and starts the platform service/task pair unless skipped
Windows note: if Redis is not detected and .env is newly created, the Windows installer sets QUEUE_BACKEND=memory for first-run compatibility. For production, install a Redis-compatible service and switch it back to redis.
Run installer:
chmod +x installer.sh
./installer.shNative Windows:
.\install.ps1
# or
.\installer.cmdCommon options:
./installer.sh --service-user mailer
./installer.sh --app-dir /opt/mailFastApi
./installer.sh --skip-system-deps
./installer.sh --skip-service
./installer.sh --skip-npmInstaller output includes a colored ASCII banner and project GitHub link.
The real updater is scripts/updater.js; updater.sh is a compatibility wrapper.
It applies updates with a locked, fast-forward-only flow, syncs dependencies, runs syntax checks, restarts services/tasks, runs health checks, and rolls back to the previous commit if a post-merge step fails.
npm run update:check
npm run update:apply
# Compatibility wrapper
./updater.sh --check
./updater.sh --apply --yesRelease modes:
UPDATER_RELEASE_MODE=branchupdates from the configured upstream branch.UPDATER_RELEASE_MODE=tagupdates to the latest tag matchingUPDATER_ALLOWED_TAG_PATTERN.- Set
UPDATER_REQUIRE_SIGNED_TAG=truein tag mode to require signed annotated tags.
The web monitor links to the legacy Update Control screen. That screen checks updates without browser confirm/alert prompts and shows updater steps with a progress bar. If a service account cannot find npm, set UPDATER_NPM_BIN or let the updater use the npm-cli.js bundled next to the active Node.js runtime.
npm testLatest local verification summary is available in:
2026-05-27 local report:
| Check | Result |
|---|---|
npm test |
17 suites, 73 tests, 72 passed, 0 failed, 1 skipped |
| Node syntax checks | passed for core, settings, web, monitor, secure store, worker, updater |
git diff --check |
passed |
| Web visual smoke | Playwright screenshots captured under docs/assets/ |
| Autocannon load smoke | HTTP 202 responses recorded; no non-2xx status bucket reported; avg latency 7.15 ms |
| Overload dry-run | current plan calculated as 150 attempts, 20 concurrency, no real mail sent |
The load smoke test uses a temporary API-only instance with the worker disabled, so it validates queue acceptance and API overhead without sending real email.
Real SMTP test:
npm test mailsendLoad test templates:
k6 run Tests/load/k6-send.js
ACCESS_TOKEN=<JWT_TOKEN> npm run test:load:autocannon
npm run test:overloadAutocannon options:
BASE_URL=http://127.0.0.1:3000ACCESS_TOKEN=<JWT_TOKEN>CONNECTIONS=50DURATION=30OVERALL_RATE=100TEST_TO=load@example.com
The autocannon helper writes a temporary HAR file so Authorization: Bearer <token> is passed
without fragile shell quoting.
Real SMTP overload test:
# Plan only; no real mail is sent.
npm run test:overload
# Current local API limit profile: RATE_LIMIT_MAX=120, so default total is 150 attempts.
OVERLOAD_CONFIRM_REAL_SEND=true npm run test:overload
# Provider-owned SMTP stress profile. Raise RATE_LIMIT_MAX first if API limit should not be the bottleneck.
OVERLOAD_PROFILE=smtp-provider OVERLOAD_TOTAL=1000 OVERLOAD_CONCURRENCY=50 OVERLOAD_CONFIRM_REAL_SEND=true npm run test:overload
# Heavier payload test. This sends 1 MB HTML bodies through the MailFastApi endpoint.
OVERLOAD_PROFILE=smtp-provider OVERLOAD_TOTAL=1000 OVERLOAD_CONCURRENCY=100 OVERLOAD_HTML_MB=1 OVERLOAD_CONFIRM_REAL_SEND=true npm run test:overload
# Max-performance profile for provider-owned infrastructure.
OVERLOAD_PROFILE=max-performance OVERLOAD_TOTAL=2000 OVERLOAD_CONCURRENCY=150 OVERLOAD_HTML_MB=2 OVERLOAD_CONFIRM_REAL_SEND=true npm run test:overloadOverload script behavior:
- Reads auth and
TEST_MAIL_TOfrom.env. - Sends through
POST /send, not directly through Nodemailer. - Uses
OVERLOAD_SMTP_ACCOUNT/SMTP_ACCOUNTto force a specific encrypted SMTP account. - Uses
OVERLOAD_HTML_MBorOVERLOAD_HTML_BYTESto control the per-message HTML payload size. - Defaults to dry-run until
OVERLOAD_CONFIRM_REAL_SEND=trueis set. - Writes a JSON report under
logs/overload-report-*.jsonunlessOVERLOAD_REPORT_PATHis set. - Masks the test recipient in console/report target summary.
With the current development .env, the first meaningful overload number is 150 attempts:
RATE_LIMIT_MAX=120 per 60000ms, plus 25 percent headroom to verify 429 behavior. Because the SMTP
provider is under your control, the second-stage provider stress number is 1000 attempts after increasing
API rate limits enough that MailFastApi can queue the burst. The max-performance profile is intended only
for controlled provider-owned infrastructure because MB-sized payloads can generate gigabytes of real mail
traffic quickly.
Retry failed/dead-lettered mail:
# Validate pending failed jobs without queueing them.
npm run retry:failed -- --dry-run --limit 100
# Requeue the latest pending failed jobs.
npm run retry:failed -- --limit 100
# Retry specific DLQ ids. Use --force only after confirming the recipient should be retried.
npm run retry:failed -- --ids 12,13The retry flow keeps the original DLQ row for audit and marks it with the new queued job id. Hard-bounce
and suppressed recipients are skipped unless --force is used.
By default, workers also retry pending failed/dead-lettered jobs in the background:
DLQ_AUTO_RETRY_ENABLED=trueDLQ_AUTO_RETRY_MAX_ATTEMPTS=3DLQ_AUTO_RETRY_INTERVAL_MS=30000DLQ_AUTO_RETRY_BATCH_SIZE=100
After the configured automatic attempts are exhausted, the DLQ row is marked as a final error and remains
available for audit/manual review. For aggressive local/provider-owned stress runs, set
WORKER_RESOURCE_MODE=max and a high WORKER_CONCURRENCY; actual throughput is still bounded by CPU,
network, SQLite/Redis, SMTP pool settings, and the SMTP provider.
After traffic exists, render CLI dashboard:
npm run log mailsenderDashboard includes:
- 24h totals (
mail sent,mail failed, retries) - event/level distributions
- SMTP latency stats
- per-minute throughput graph
- recent structured logs
Endpoint details are in:
Multi-account send example, after adding the account in the web panel:
curl -X POST http://localhost:3000/send \
-H "Authorization: Bearer <JWT_TOKEN>" \
-H "Content-Type: application/json" \
-d "{\"smtpAccount\":\"2fa\",\"to\":\"user@example.com\",\"subject\":\"Kod\",\"html\":\"<p>123456</p>\"}"Before publishing a public release:
- Keep
LICENSEas GNU GPL v3.0. - Keep
.env,data/,logs/, and secure SQLite vaults out of git. - Publish
.env.example, docs, tests, and screenshots so users can reproduce the setup. - Prefer signed git tags for releases.
- Run
npm test, syntax checks, and at least one load smoke before tagging. - Do not publish real SMTP credentials, private DKIM keys, monitor tokens, updater tokens, or secure-store keys.
Recommended public release flow:
npm test
node --check src/app.js
node --check src/web.js
node --check src/monitor.js
node --check src/secureStore.js
node --check src/worker.js
node --check scripts/updater.js
git diff --check
git tag -s vX.Y.Z -m "MailFastApi vX.Y.Z"
git push origin main --tagsCommunity contributions should include tests for behavior changes and documentation for operational or security-sensitive features. For mail-delivery changes, include the expected impact on queueing, idempotency, suppression, rate limiting, and deliverability.



