This project (maintained by a member of Haltman.io) is a mail forwarding service API built with Node.js and Express. It exposes HTTP endpoints that allow users to:
-
request alias creation (subscribe)
-
confirm alias creation via email token (confirm)
-
request alias removal (unsubscribe)
-
confirm alias removal via email token (unsubscribe confirm)
Key points:
-
This API does not receive emails.
-
It only manages forwarding rules (aliases) by writing directly into the same MariaDB database used by the Base Stack (Postfix + MariaDB).
-
All operations are rate-limited, abuse-resistant, and confirmed via email tokens.
Important prerequisite (mandatory)
This API does not work standalone.
Before deploying this service, you must deploy the Base Stack first:
-
Postfix + MariaDB schema (domains + aliases)
-
PostSRSd (SRS)
Read first:
Installation
Requirements
-
Node.js (current LTS recommended)
-
A running Base Stack (MariaDB reachable with correct schema)
-
Working SMTP credentials (required to send confirmation emails)
-
Redis (optional, recommended for production rate limiting)
Install
git clone https://github.com/haltman-io/mail-forwarding.git
cd ./mail-forwarding/app
npm install
Configuration
Create .env
This project does not ship with a .env file.
cp .env.example .env
If .env.example is not present, create .env manually.
Minimal required .env settings
Application
APP_ENV=prod
APP_PORT=3000
TRUST_PROXY=1
APP_PUBLIC_URL=https://api.example.org
EMAIL_CONFIRM_CONFIRM_ENDPOINT=/forward/confirm
Notes:
-
TRUST_PROXYmust reflect how many reverse proxies are in front of the API (affects IP-based rate limiting). -
APP_PUBLIC_URLis used to build confirmation links.
MariaDB (Base Stack database)
MARIADB_HOST=127.0.0.1
MARIADB_PORT=3306
MARIADB_USER=...
MARIADB_PASSWORD=...
MARIADB_DATABASE=...
These must point to the same database used by the Base Stack.
SMTP (required)
SMTP_HOST=...
SMTP_PORT=587
SMTP_SECURE=false
SMTP_AUTH_ENABLED=true
SMTP_USER=...
SMTP_PASS=...
SMTP_FROM="Mail Forwarding <no-reply@example.org>"
SMTP_TLS_REJECT_UNAUTHORIZED=true
Without SMTP, confirmation emails will not be sent, and the service is effectively unusable.
Tokens (minimum)
EMAIL_CONFIRMATION_TTL_MINUTES=10
EMAIL_CONFIRMATION_RESEND_COOLDOWN_SECONDS=60
EMAIL_CONFIRMATION_TOKEN_LEN=12
Default alias domain (optional but recommended)
DEFAULT_ALIAS_DOMAIN=example.org
Used when the user does not pass ?domain=.
Redis (optional, recommended for production)
REDIS_URL=redis://127.0.0.1:6379
REDIS_RATE_LIMIT_PREFIX=rl:
REDIS_CONNECT_TIMEOUT_MS=5000
If REDIS_URL is empty, rate limiting uses in-memory storage (not shared across instances).
Running
node ./source/server.js
(Optional) using PM2:
pm2 start ./source/server.js --name mail-forwarding-api --no-daemon
How to use (HTTP endpoints)
Important rules:
-
All endpoints are GET-only
-
All inputs are query parameters
-
No JSON body / POST is used anywhere
1) Create alias request
GET /forward/subscribe
Example:
/forward/subscribe?name=github&to=user@gmail.com&domain=example.org
Parameters:
-
name(required): alias local-part -
to(required): destination mailbox -
domain(optional): alias domain (defaults toDEFAULT_ALIAS_DOMAIN)
Expected responses (common):
-
200 ok(confirmation email sent) -
400 invalid_input -
409 alias_taken -
429 rate_limited -
403 banned
2) Confirm alias creation
GET /forward/confirm?token=...
Example:
/forward/confirm?token=AbC123xYz
Expected responses (common):
-
200 confirmed -
400 invalid_token -
404 token_not_found -
410 token_expired -
429 rate_limited
3) Remove alias request
GET /forward/unsubscribe
Example:
/forward/unsubscribe?address=github@example.org
Parameters:
address(required): full alias address
Expected responses (common):
-
200 ok(confirmation email sent) -
404 not_found -
429 rate_limited -
403 banned
4) Confirm alias removal
GET /forward/unsubscribe/confirm?token=...
Example:
/forward/unsubscribe/confirm?token=ZyX987Ab
Expected responses (common):
-
200 removed -
400 invalid_token -
404 token_not_found -
410 token_expired -
429 rate_limited
Possible problems / Important notes
-
Base Stack not deployed: API may start, but operations will fail (missing schema / wrong DB).
-
SMTP misconfigured: subscribe/unsubscribe will not complete (no confirmation emails).
-
Behind proxy without TRUST_PROXY: rate limiting may treat all users as the same IP.
-
No Redis in production: multi-instance deployments will have inconsistent rate limiting.
-
Destructive behavior: this service writes directly into mail routing tables. Test in staging first.