Mail Handler
Self-hosted disposable email and OTP retrieval — SMTP ingest through to an authenticated API, on infrastructure you own.
// By the numbers
- 15
- Permission keys
- 11
- Scoring signals
- 3
- Deployable units
- 5 min
- Default retention
- 2
- Privileged commands
granular, database-backed, editable at runtime without a redeploy
weighted signals decide whether a candidate is really a verification code
mail ingest, API and dashboard, each scaling independently
messages purged on a 60-second cycle — data minimisation by construction
the entire sudo grant: postmap, and a Postfix reload
What the business was up against
Any automated flow that has to read real email hits the same wall twice. First, the mail has to arrive somewhere you control — which means running a mail server, not just calling one. Second, and harder, something has to find the verification code inside a message written for a human. A six-digit regex looks like it works until it meets production mail, where order numbers, tracking IDs, card fragments and query-string parameters all match the same pattern, and the code arrives as Arabic-Indic numerals or HTML entities often enough to matter. Getting the wrong code back is worse than getting nothing: a miss can be retried, while a confident wrong answer silently breaks whatever was waiting on it.
A self-hosted platform that owns the whole path from SMTP delivery to a JSON response. Postfix accepts mail on any number of catch-all domains and pipes each message to a Node process that parses it, extracts a verification code, stores it under a short retention window, and exposes it over an API keyed per consumer. An Arabic-first dashboard handles domains, addresses, keys and permissions. Three services, one database, one server.
How it's put together
Postfix MTA
Catch-all domains on port 25
Pipe Transport
One stateless Node process per message
Extraction Engine
Candidate scoring with confidence gates
REST API
Address provisioning · code retrieval
Admin Dashboard
Next.js, Arabic-first
Permission Gate
15 keys, resolved per route
MongoDB
Domains, addresses, keys, audit log
Settings Document
Retention, whitelist, role mapping
Retention Job
Purge cycle every 60 seconds
DNS Monitor
Hourly MX and A verification
Webhooks
Push a code the moment it lands
One database is the control plane — ingest, API and dashboard all read their behaviour from it.
The calls that shaped it
The handful of decisions that determined how this system behaves — and what each one bought.
- 01
Score candidates, don't match them
Four pattern families generate candidates, then eleven weighted signals decide between them — proximity to context words, keyed prefixes like 'code:', a length prior peaking at six digits, and penalties for candidates sitting inside a URL or next to phrases like 'ending in' and 'tracking'. Extraction is a ranking problem, not a matching one, and treating it as matching is why the naive version fails.
- 02
Refuse to guess
Two independent gates stand between a candidate and the API: an absolute confidence floor, and a competition margin that rejects a winner too close to the runner-up unless it scores highly on its own. Below either, the engine returns nothing at all. This is a deliberate precision-over-recall trade — a missed code is retryable, a wrong code fails silently downstream.
- 03
Treat Arabic as the default case
Arabic-Indic and Persian numerals normalise to ASCII before scoring, HTML numeric entities are decoded because senders genuinely use them to obfuscate, and the hint vocabulary runs bilingual across both languages. Senders in this market do all three, so handling them is table stakes rather than polish.
- 04
The database is the control plane
Retention windows, sender whitelists, branding and the entire permission matrix live in one settings document that all three services read through a short-lived cache. Changing how the system behaves is a write, not a deploy — which is what makes it maintainable by whoever owns the server afterwards.
- 05
Permissions are data, not code
Fifteen granular permission keys are stored in the database and mapped to roles from the settings screen, with every route declaring what it needs. A normalisation guard permanently re-grants the admin's own settings permissions, so an operator cannot revoke their way into a lockout — the system is designed against operator error, not just attackers.
- 06
Mail server config is application state
Adding a domain writes the Postfix map files, runs postmap and reloads the service through a sudo grant scoped to exactly two binaries. An hourly job then verifies the MX and A records actually resolve and flags the domain in the UI when they don't. Domain setup is the step most likely to be done wrong by hand, so it was worth automating end to end.
- 07
Stateless ingest
Postfix spawns one short-lived process per message, holding no state between them, so ingest throughput scales by raising the transport's concurrency rather than by rewriting anything. Secondary work — domain counters, webhooks, audit logging — fails soft, because a message should never be lost to a failure in something that isn't receiving it.
Signature capabilities
Confidence-scored extraction
Four candidate patterns and eleven weighted signals rank every possible code in a message, with penalties for candidates inside URLs or beside phrases like 'ending in' — the two largest sources of false positives in real mail.
Silence over a wrong answer
A confidence floor and a competition margin both have to clear before a code is returned. When the message is ambiguous the API returns nothing, because a retryable miss costs far less than a confident mistake.
Bilingual by construction
Arabic-Indic and Persian numerals normalise before scoring, HTML numeric entities decode, and context hints are recognised in Arabic and English alike — so an Arabic sender is the normal case rather than an edge case.
Catch-all domains, unlimited addresses
Any number of domains can be attached, each accepting mail at every address. Adding one writes the Postfix maps, reloads the service and starts verifying its DNS on an hourly cycle.
Per-key isolation
Every API key carries its own allowed domains, request-rate ceiling, lifetime quota and address cap, and is stored only as a hash — the raw key is shown once and never persisted.
Runtime-editable permissions
Fifteen permission keys map to roles from the settings screen and take effect within seconds, with the admin's own settings access structurally non-revocable so nobody can lock themselves out.
Business feature set
The functional scope delivered across every part of the product.
Mail handling
- Catch-all delivery on any number of domains
- Full RFC-822 parsing of every received message
- Sender whitelisting by address or whole domain
- Configurable retention with automatic purging
- Webhook push the moment a code is extracted
Code extraction
- Numeric, spaced-digit, prefixed and alphanumeric candidates
- Context-aware scoring in Arabic and English
- URL and negative-hint penalties
- Confidence floor and competition margin
- Arabic-Indic and Persian numeral normalisation
- HTML entity decoding and plain-text fallback
API
- Provision an address on demand
- Retrieve the latest code within a time window
- Automatic balancing across the domain pool
- Per-key rate limiting and quotas
- Interactive API documentation
Dashboard
- Domain management with DNS setup guidance
- Address and message browsing
- API key issuance and revocation
- Role and permission editing
- Activity log across every mutation
- Overview metrics for host, database and usage
Operations
- systemd services with sandboxing and restart policies
- Nginx reverse proxy with TLS
- Hourly DNS health verification
- Rotating, compressed application logs
- Scoped sudo grant for mail server reloads
- Load-test harness across the full SMTP-to-API path
What changed operationally
Documented outcomes from the delivered system. Where exact figures are confidential, the operational change is stated instead.
- Runs on a single VPS: Postfix, MongoDB, an API service and a dashboard under systemd behind Nginx
- Unlimited addresses across any number of catch-all domains, with no per-inbox cost
- Verification codes returned over an authenticated API, or pushed to a webhook on arrival
- Per-key domain scoping, rate ceilings, lifetime quotas and address caps
- Fifteen-key permission matrix editable from the dashboard without a redeploy
- Messages purged on a 60-second cycle against a configurable retention window
- Hourly DNS health checks that flag misconfigured domains before mail is lost
- Full audit trail of every mutation, including actions taken by API keys
What we had to work within
- Mail has to be received, not polled — the system runs an MTA rather than calling someone else's
- Verification codes are single-use and short-lived, so extraction has to be right the first time
- Arabic senders send Arabic-Indic numerals and HTML-encoded digits as a matter of course
- Handed to the client to own, so runtime behaviour had to be changeable without a developer
- Message content is sensitive and short-lived by design — retention is enforced in code
- Single-server budget: everything had to co-exist on one modest VPS
What I owned
End-to-end on this project — from architecture and data modelling through to a shipped, production product.
- Mail server configuration and pipe transport integration
- OTP extraction engine — candidate generation, scoring and gating
- REST API, authentication, rate limiting and quotas
- Permission model and settings control plane
- Next.js dashboard, Arabic-first and right-to-left
- VPS provisioning, systemd services, Nginx, TLS and firewall
- Monitoring, log rotation and backup strategy
Built with
Backend
Data
Frontend
Infrastructure
Run this on your own server
This system is built and running, so it does not need to be rebuilt to be yours. I install it on a VPS you own, attach your domains, configure DNS and TLS, and hand it over with the credentials and the documentation. You get unlimited addresses on your own infrastructure, and no message ever leaves it.
- Installed on your VPS — Postfix, database, API and dashboard, under systemd behind Nginx with TLS
- Your domains attached and verified, with the DNS records set up with you
- Admin account, API keys and a walkthrough of the dashboard at handover
- Optional retainer for updates, monitoring, backups and domain changes
Priced per engagement — message me with your domains and expected volume and I'll quote it.
Want something like Mail Handler?
Boutique software studio — agency-quality systems, shipped by one senior engineer.
// Related work


