Skip to content
All work
// Case studyBackendlive

Mail Handler

Self-hosted disposable email and OTP retrieval — SMTP ingest through to an authenticated API, on infrastructure you own.

2025 Egypt Email infrastructure · Test automation
Email infrastructureTest automationServer administration
Build something similar

// By the numbers

15
Permission keys

granular, database-backed, editable at runtime without a redeploy

11
Scoring signals

weighted signals decide whether a candidate is really a verification code

3
Deployable units

mail ingest, API and dashboard, each scaling independently

5 min
Default retention

messages purged on a 60-second cycle — data minimisation by construction

2
Privileged commands

the entire sudo grant: postmap, and a Postfix reload

// The problem

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.

// Overview

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.

// Architecture

How it's put together

01Mail ingest

Postfix MTA

Catch-all domains on port 25

Pipe Transport

One stateless Node process per message

Extraction Engine

Candidate scoring with confidence gates

02Application layer

REST API

Address provisioning · code retrieval

Admin Dashboard

Next.js, Arabic-first

Permission Gate

15 keys, resolved per route

03Data

MongoDB

Domains, addresses, keys, audit log

Settings Document

Retention, whitelist, role mapping

04Operations

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.

// Engineering decisions

The calls that shaped it

The handful of decisions that determined how this system behaves — and what each one bought.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

// Highlights

Signature capabilities

01

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.

02

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.

03

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.

04

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.

05

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.

06

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.

// Features

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
// Outcome

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
// Constraints

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
// My role

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
// Stack

Built with

Mail

PostfixmailparserSMTP

Backend

Node.jsExpressMongoose

Data

MongoDB

Frontend

Next.jsTypeScriptTailwind CSS

Infrastructure

LinuxNginxsystemdLet's Encrypt
// Available to deploy

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
Message me about this

Priced per engagement — message me with your domains and expected volume and I'll quote it.

// Makhloof Studio

Want something like Mail Handler?

Boutique software studio — agency-quality systems, shipped by one senior engineer.

Build something like this
WhatsApp