Skip to content

samsonchim/mulul

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mulul Mail

Open-source, zero-storage custom domain email delivery infrastructure.

Mulul Mail puts a thin, type-safe Next.js (App Router) API gateway in front of the Stalwart mail core (a modern, high-performance mail server written in Rust). The gateway accepts JSON over HTTP, verifies that the sender's domain provably belongs to you (live SPF/DKIM DNS checks), maps the payload into native JMAP (Email/set + EmailSubmission/set), relays it over SMTP, and purges the message the instant submission succeeds. No mailboxes, no archive, no plaintext left behind.

client ──JSON──▶  app-gateway (Next.js)  ──JMAP──▶  Stalwart  ──SMTP──▶  recipient
                  │ verify domain                  │ sign (DKIM)
                  │ map → Email/set                │ relay
                  └ purge references               └ onSuccessDestroyEmail

Layout

.
├── compose.yml            # Production orchestration (stalwart + app-gateway)
├── compose.dev.yml        # Local dev override (mocked DNS, live reload)
├── .env.example           # Documented environment template
├── setup.sh               # One-command live-host bootstrap
└── app-gateway/           # Next.js gateway + portal
    ├── Dockerfile         # Multi-stage standalone build
    ├── next.config.js     # CORS + /jmap reverse proxy
    └── src/
        ├── app/
        │   ├── api/domains/route.ts   # Multi-tenant domains + DKIM
        │   ├── api/send/route.ts      # Ephemeral JMAP relay
        │   ├── api/health/route.ts    # Liveness probe
        │   └── page.tsx               # Enterprise control portal
        └── lib/           # env, auth, dns, stalwart JMAP client

Quick start (production VPS)

git clone <your-fork> mulul-mail && cd mulul-mail
chmod +x setup.sh && ./setup.sh        # interactive: writes .env, builds, boots

setup.sh checks for Docker + the Compose plugin, generates a .env (prompting for your mail hostname, public IP, and admin credentials, auto-generating secrets), and starts the stack.

Then open http://<server-ip>:3000 for the portal.

Local development sandbox

cp .env.example .env
docker compose -f compose.yml -f compose.dev.yml up --build

In dev mode (NODE_ENV=development) all domain/SPF/DKIM verification returns mocked passing results so you can exercise the full send pipeline without owning DNS. Source is bind-mounted with live reload.

API

All endpoints require Authorization: Bearer <GATEWAY_API_KEY>.

POST /api/send

{
  "from": "[email protected]",   // domain must be verified
  "to": ["[email protected]"],
  "cc": [], "bcc": [],
  "subject": "Hello",
  "text": "Plain body",         // text and/or html
  "html": "<p>Rich body</p>",
  "replyTo": "[email protected]"
}

Response: { ok, submissionId, domain, storage: "purged", ... }.

GET /api/domains · POST /api/domains

POST body { "domain": "acme.com", "selector": "default" } registers the domain in Stalwart, provisions DKIM keys, and returns the SPF/DKIM/DMARC records to publish. GET lists all domains with a live verification report.

Ports

Port Service Purpose
25 Stalwart SMTP inbound + outbound relay
465 Stalwart SMTPS (implicit TLS)
587 Stalwart Submission (STARTTLS)
8080 Stalwart JMAP / management API (internal)
3000 app-gateway Portal + REST API

Production checklist

  • DNS: A record for MAIL_DOMAIN → server IP, plus matching PTR/rDNS.
  • Open egress on port 25 (many cloud providers block it by default).
  • Publish per-tenant SPF, DKIM and DMARC records (see POST /api/domains).
  • Front the gateway with TLS (reverse proxy / Caddy / nginx) before exposing 3000.
  • Keep .env (chmod 600) out of version control — it is git-ignored.

License

MIT.

About

Custom Domain Email Infrastructure

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors