Skip to content

Latest commit

 

History

53 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MailSender (MailCouse)

License Language Stars Last commit Repo size

A self-hosted, Postal-inspired multi-tenant email delivery and cold-outreach infrastructure platform.

MailSender (internal project name mailcouse) is a TypeScript email infrastructure system that replicates the core feature set of the open-source Postal mail server: multi-tenant organizations, scoped mail servers, an inbound SMTP relay, DKIM signing, domain/subdomain verification with live DNS checks, IP-style subdomain pools, message inspection, suppressions, and webhooks. It is architected for engagement-first cold outreach at up to 100,000 emails/day across 50 root domains, with reputation-safe volume distribution via subdomain identities.

Overview

The platform follows an "engagement-first" architecture: every sending identity stays under conservative per-SMTP daily caps (10 emails/day post-warmup, 200 subdomain SMTPs per root domain, 2,000 emails/domain/day total) so that behavioral trust is built before scale. The application implements the full delivery pipeline itself: lead ingestion with validation and deduplication, spintax content generation, queue-based dispatch through pooled subdomain credentials, IMAP-based bounce and reply detection, suppression handling, and a Postal-replica web UI built with EJS.

Detailed engineering specs and implementation designs for domain/subdomain verification, DNS pipelines, and relaying are documented under docs/superpowers/.

Features

  • Multi-tenant portals with authentication (JWT + bcrypt), organizations, servers, and scoped dashboards
  • Built-in SMTP relay (smtp-server) with dedicated listen ports per traffic class: mass mail (587), personal (588), transactional (589)
  • DKIM signing and SPF-aligned root-domain envelope-from on outbound messages
  • Subdomain-aware domain resolution: sending from user@sub.example.com resolves to the most specific verified parent domain and signs with its DKIM key
  • DNS verification pipeline (SPF, MX, DKIM, return-path CNAME, postal-verification records) with trailing-dot normalization
  • Cloudflare API-driven DNS provisioning scripts and bulk subdomain provisioning/statistics tooling
  • Warmup engine: scheduler, gate, activator, monitor, plus optional external Warmbox API integration
  • Bounce and complaint processing via IMAP polling (mailparser), suppression list enforcement
  • Engagement tracking: open/click tracking routes, fingerprints, counters, response classification, threading
  • Lead ingestion from CSV/API with staged validation, deduplication, industry segmentation, and enrichment provider hooks (Prospeo, Blitz, RapidAPI, DiscoLike)
  • Spintax-extended content rotation for message uniqueness
  • Webhooks, message detail views with delivery attempts, held/outgoing/incoming/queued scopes, track-domain SSL checks
  • Postal-replica light-theme UI (EJS layouts, BEM CSS) with sidebar navigation, server header metrics, subdomain and pool views
  • Monitoring dashboard with message rates, cron runner, and Telegram alert notifications
  • Jest test suite covering validators, deduplicator, importer, subdomain resolution, and API integration flows

Tech Stack

Layer Technology
Language TypeScript (Node.js, CommonJS), Go (verifier utility)
Web framework Express 5, express-ejs-layouts, EJS views
SMTP smtp-server (relay), nodemailer (submission)
IMAP / Parsing imap server module, mailparser
Database PostgreSQL (pg pool)
Cache / Queue Redis (ioredis), custom worker and cron runners
Auth / Security jsonwebtoken, bcryptjs, helmet, express-rate-limit, cookie-parser
Content spintax-extended, csv-parse, multer
DNS / Deliverability Cloudflare API (node-fetch), custom dkim/dns modules, MXToolbox checks
Notifications Telegram Bot API
Testing Jest, ts-jest, Supertest
Docs Architecture specs, docs/superpowers specs, API schema

Architecture

The entrypoint (src/index.ts) wires Express middleware (helmet, compression, morgan, rate limiting, cookie sessions), mounts REST routers (api/routes, admin-routes, send-routes, auth-routes, portal-routes, tracking-routes, health routes), and renders the EJS portal. Separate long-running concerns are started alongside the HTTP server: the cron runner (warmup scheduling, retries, notifications), the bounce handler (IMAP poller), and the SMTP relay listener.

Outbound flow: lead selection and segmentation -> content builder (spintax personalization) -> queue worker -> connection-pooled sender bound to the correct outbound IPv4/IPv6 -> DKIM signature applied -> delivery through the per-traffic-class SMTP port -> session logging and retry management. Inbound feedback flow: IMAP fetch -> parse -> bounce/complaint classification -> suppression update -> counter/engagement metrics -> dashboard and Telegram alerts.

Domain trust flow: Cloudflare provisioning creates verification, SPF, DKIM, and return-path records; the verifier performs live DNS checks with normalized hostnames; the subdomain resolver picks the longest verified match so subdomains inherit the root domain's DKIM identity without multiplying reputation units (root-domain reputation is shared across all 200 subdomains per capacity formula).

Project Structure

mailsender/
├── README.md                              # Project documentation & setup guide
├── LICENSE                                # MIT license
├── docs/
│   └── superpowers/
│       ├── plans/                         # Domain and subdomain setup plans
│       └── specs/                         # Design specs (verification, relaying)
└── mailcouse/                             # MailSender application core
    ├── package.json                       # Build, test, and provisioning scripts
    ├── jest.config.js, tsconfig.json
    ├── scripts/                           # Asset copying and build scripts
    ├── tools/
    │   └── email-verifier/                # High-performance Go email verification tool
    └── src/
        ├── index.ts                       # Express bootstrap, portal UI, lifecycle
        ├── api/                           # REST + portal routes, domain logic, auth
        ├── smtp/                          # Relay, connection pool, email builder, sender
        ├── warmup/                        # Scheduler, gate, activator, monitor
        ├── dns/                           # Provisioner, record builder, verifier
        ├── dkim/                          # Signing + key encryption
        ├── bounce/ complaint/             # Feedback loops & error handling
        ├── imap/ ingestion/               # Mailbox polling, lead sources
        ├── validation/                    # Staged email and MX validation
        ├── verification/                  # Pre-send verification client, cache & policy
        ├── segmentation/ suppression/     # Audience + exclusion management
        ├── content/                       # Spintax rendering engine
        ├── queue/ worker/ cron/           # Asynchronous dispatch pipeline
        ├── engagement/ fingerprint/       # Opens, clicks, threading, reply tracking
        ├── counters/ monitoring/          # Metrics + HTML dashboard
        ├── cloudflare/                    # Cloudflare DNS API client
        ├── db/ config/ scripts/           # DB pool, typed config, seed/provision CLIs
        ├── public/ views/                 # Postal-replica assets and EJS templates
        └── **/__tests__/                  # Unit and integration test suites

Getting Started

Prerequisites

  • Node.js 18+ (TypeScript 5.x toolchain via ts-node)
  • PostgreSQL 14+
  • Redis 6+
  • A VPS with outbound mail ports (25, 587) and rDNS configured for production sending
  • Cloudflare account API token for automated DNS provisioning (optional but recommended)

Installation

cd mailcouse
npm install

# Build (compiles TypeScript and copies static assets/views to dist/)
npm run build

# Provision a fresh database with domains, server, and credentials
npm run seed

# Optional infrastructure helpers
npm run dns:provision          # create verification/SPF/DKIM/return-path records via Cloudflare
npm run subdomains:provision   # bulk-create subdomain sending identities
npm run subdomains:stats       # report active/inactive subdomains per root domain
npm run setup:vps              # VPS baseline setup script

Environment Variables

Copy your secrets into .env (names below are read by src/config/index.ts; values are placeholders):

Variable Placeholder
NODE_ENV development
DB_HOST / DB_PORT / DB_NAME / DB_USER / DB_PASSWORD localhost / 5432 / mailcouse / postgres / postgres
DB_SSL false
REDIS_PRIMARY_HOST / REDIS_BACKUP_HOST / REDIS_PORT / REDIS_PASSWORD / REDIS_TLS localhost / (empty) / 6379 / (empty) / false
API_PORT / API_HOST 3000 / 0.0.0.0
JWT_SECRET change-me-to-a-long-random-string
SMTP_PORT / SMTP_PORT_ALT 587 / 25
SMTP_PORT_MASS / SMTP_PORT_PERSONAL / SMTP_PORT_TRANSACTIONAL 587 / 588 / 589
IMAP_ENABLED / IMAP_PORT / IMAPS_PORT true / 143 / 993
SMTP_TLS_CERT (or SMTP_TLS_FULLCHAIN) / SMTP_TLS_KEY (or SMTP_TLS_PRIVKEY) /path/fullchain.pem / /path/privkey.pem
OUTBOUND_IPV4 (or OUTBOUND_LOCAL_ADDRESS) / OUTBOUND_IPV6 203.0.113.10 / (empty)
CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID cf-token-placeholder / cf-account-placeholder
PROSPEO_API_KEY / BLITZ_API_KEY / RAPIDAPI_KEY / DISCOLIKE_API_KEY provider-key-placeholder
WARMBOX_API_KEY / WARMBOX_ACCOUNT_ID / WARMUP_API_URL wb-key-placeholder / wb-account-placeholder / https://api.warmbox.com/v1
MXTOOLBOX_API_KEY mxtoolbox-key-placeholder
TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID tg-token-placeholder / tg-chat-placeholder
DKIM_ENCRYPTION_KEY dkim-key-encryption-placeholder
DNS_DKIM_IDENTIFIER / DNS_DOMAIN_VERIFY_PREFIX / DNS_CUSTOM_RETURN_PATH_PREFIX postal / postal-verification / psrp
IMPORT_MAX_BATCH / DNS_TIMEOUT 1000 / 5

Running

# Development (ts-node, hot reload of source tree)
npm run dev

# Production (compiled dist/)
npm run build
npm start

# Test suite with coverage
npm test
npm run test:unit
npm run test:integration

No docker-compose file ships with this repository; deployment targets a dedicated VPS (see npm run setup:vps) where the process owns ports 25/587/143 directly.

Challenges Faced & Solutions

  • SMTP handler hung on handshake: the relay stalled when clients sent multi-line EHLO replies. Solution: rewrote the EHLO parsing path in the SMTP handler so continuation lines are consumed correctly (commit fix: SMTP handler no longer hangs (multi-line EHLO fix)), then added DKIM signing and SPF root-domain envelope-from in the same pass.
  • Subdomain sending had no trust model: messages from sub.example.com were rejected or mis-signed even when the parent domain was verified. Solution: centralized subdomain matching into a resolver that selects the longest verified parent domain, normalized trailing dots returned by nameservers during DNS checks, and routed both the portal send route and the relay's onData handler through it (commits around feat: use subdomain-aware resolver ... and feat: centralize subdomain matching ...).
  • Reputation math misunderstood: treating 200 subdomains as 200 independent reputations would have destroyed deliverability. Solution: encoded the capacity formula (2,000 emails/day ceiling shared per root domain) into provisioning limits and warmup gates rather than allowing unlimited per-subdomain volume.
  • Build output incomplete: tsc did not emit static assets, leaving the portal without CSS/JS in production. Solution: added a copy-assets step to the build script and a fix commit ensuring assets land in dist/ (fix: copy static assets to dist ...).
  • Header fidelity and spoofing: forwarded messages lost their original From header, and unverified domains could still submit mail. Solution: preserved original From passthrough and hard-rejected unverified domains at relay time; later accepted RFC display-name format (Name <address>) on the web send form.
  • Live delivery failed over IPv6: outbound connections preferred broken IPv6 routes on some VPSes. Solution: forced outbound binding via IPv4 (OUTBOUND_IPV4/OUTBOUND_LOCAL_ADDRESS) together with enabling STARTTLS on the relay (fix: enable SMTP STARTTLS, live delivery via IPv4 ...).
  • Credential UX gap: operators needed safe handoff of SMTP credentials. Solution: added copyable credential modal, per-credential sender controls, and domain diagnostics with real authentication checks.
  • Regression safety: subdomain resolution logic kept drifting. Solution: locked behavior with unit tests for parent-domain parsing and resolution, plus an integration test suite (test: add unit tests for subdomain resolution and parent domain parsing).

Known Limitations & Roadmap

  • Warmup currently integrates with an external Warmbox API for ramping; expanding the native autonomous warm-up engine is planned for future phases.
  • Phase 2 scale target (75 domains / 150k emails/day) will introduce dynamic multi-IP pool management and automated IP-reputation routing.
  • Advanced routing: extending the inbound message pipeline with a visual routing rules engine.
  • Roadmap candidates: per-tenant IP pool management UI, AI-powered reply categorization, granular webhook retry policies, and Prometheus metrics export.

Security Notes

  • JWT-based portal auth with bcrypt password hashing; portal responses set no-store cache headers.
  • Unverified domains are rejected at the SMTP relay; DKIM keys are stored encrypted using DKIM_ENCRYPTION_KEY.
  • helmet, compression, morgan, and express-rate-limit are applied globally; JSON body size is capped (10 MB).
  • Before publishing this repository, audit history for accidentally committed .env values, DKIM private keys, and Cloudflare tokens; rotate any credential that has ever been committed.
  • CSP and cross-origin embedder policies are intentionally relaxed for the embedded Postal-replica UI; tighten before exposing the portal publicly.

License

MIT License © 2026 Musfiqur Rahman Saimon. See LICENSE.

About

Postal-style multi-tenant MTA - SMTP relay ports, DKIM/SPF automation, warmup engine, 100k/day design.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages