MailX is open-source, developer-first email infrastructure built from the protocol up in Go. It accepts mail over SMTP or a tenant-scoped REST API, stores the source message, processes delivery through a durable PostgreSQL and Redis pipeline, and exposes lifecycle events through signed webhooks.
The project is intentionally built as understandable infrastructure rather than a wrapper around a third-party email provider. Its design keeps durable lifecycle state in PostgreSQL, raw message data in local storage, and transient delivery scheduling in Redis.
Warning
MailX is under active development and is not production-ready. In particular, inbound and outbound STARTTLS/TLS are planned for v0.24 and are not currently implemented. Do not expose the current SMTP or HTTP listeners to untrusted networks.
- Docker Engine with Docker Compose v2
curlfor the API example- Go 1.27.1 only when building or testing outside Docker
-
Clone the repository and enter it.
git clone https://github.com/Ferousco-dev/Mailx.git cd Mailx -
Create a local environment file.
cp .env.example .env
The checked-in defaults are for local development only. Never reuse them in a real deployment.
-
Build and start MailX, PostgreSQL, Redis, and the database migration job.
docker compose up --build -d docker compose ps
-
Confirm the API is available.
curl --fail http://localhost:8080/health/live curl --fail http://localhost:8080/health/ready
The local services are available at:
| Service | Address |
|---|---|
| REST API | http://localhost:8080 |
| Interactive API documentation | http://localhost:8080/docs |
| OpenAPI contract | http://localhost:8080/openapi.json |
| SMTP receiver | localhost:2525 |
| PostgreSQL | localhost:5432 |
| Redis | localhost:6379 |
View logs or stop the stack with:
docker compose logs -f mailx
docker compose downUse docker compose down -v only when you intentionally want to delete all local MailX, PostgreSQL, and Redis data.
An opt-in low-memory PostgreSQL profile is available for small deployments. It is not the default and it keeps PostgreSQL's durability settings on:
docker compose -f compose.yaml -f compose.low-memory.yaml up -dSee docs/low-memory-deployment.md for what it changes, the measured effect and the trade-offs.
Official SDKs live in their own repos under the UseMailx organization, not in this one — a self-hosted server checkout doesn't need client-side code in five languages sitting in it. Each SDK defaults to the hosted API's base URL but takes an override, so the same client code works against a self-hosted deployment.
| Language | Repo |
|---|---|
| Node.js / TypeScript | UseMailx/mailx-node |
| Python | UseMailx/mailx-python |
| Go | UseMailx/mailx-go |
| PHP | UseMailx/mailx-php |
| Ruby | UseMailx/mailx-ruby |
Every /v1 endpoint requires a scoped MailX API key. Tenant and key management are deliberately operator-only CLI operations.
-
Create a tenant.
docker compose exec mailx /app/mailx create-tenant -name local-development -
Create a key using the
tenant_idprinted by the previous command.docker compose exec mailx /app/mailx create-api-key \ -tenant TENANT-ID \ -name local-client \ -scopes emails:send,emails:read,domains:read,domains:write,webhooks:read,webhooks:writeSave the printed
keyimmediately. MailX displays the raw key only once. -
Submit an email using that key.
export MAILX_API_KEY='PASTE-THE-KEY-HERE' curl --request POST http://localhost:8080/v1/emails \ --header "Authorization: Bearer ${MAILX_API_KEY}" \ --header 'Content-Type: application/json' \ --header 'Idempotency-Key: local-example-001' \ --data '{ "from": "sender@example.com", "to": ["recipient@example.net"], "subject": "Hello from MailX", "text": "MailX accepted this message for asynchronous delivery." }'
An HTTP 202 Accepted response means MailX durably recorded responsibility for the message. It does not mean that a remote SMTP server accepted the message or that it reached an inbox. Use GET /v1/emails/{id} and GET /v1/events to inspect durable lifecycle state.
For the complete API, including domains, webhooks, pagination, scopes, and error schemas, open the interactive documentation after starting MailX.
MailX separates durable truth from scheduling:
- PostgreSQL owns tenants, API keys, email lifecycle state, delivery attempts, domains, idempotency records, immutable events, and webhook deliveries.
- Local file storage preserves raw messages and extracted attachment data.
- Redis coordinates available and leased jobs; it is not the authoritative delivery-history store.
- Workers persist delivery outcomes before acknowledging or releasing queue jobs.
- Webhooks observe immutable events and never change email delivery state.
This architecture provides at-least-once processing. MailX does not claim exactly-once SMTP delivery: a crash after remote acceptance but before local outcome persistence is inherently ambiguous.
The complete development configuration is documented in .env.example. The main runtime settings are:
| Variable | Purpose | Local default |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string; enables the full API, queue, worker, and webhook runtime | Set by Compose |
REDIS_ADDR |
Redis queue address | localhost:6379 outside Compose |
MAILX_HTTP_ADDR |
HTTP API listener | :8080 |
MAILX_SMTP_ADDR |
SMTP listener | :2525 |
MAILX_STORAGE_ROOT |
Raw message and attachment storage directory | MailX storage default |
MAILX_API_KEY_PEPPER |
Optional HMAC pepper for stored API-key verifiers | Development-only value in Compose |
MAILX_WEBHOOK_MASTER_KEY |
Base64-encoded 32-byte key used to protect webhook secrets | Development-only value in Compose |
MAILX_WEBHOOK_ALLOW_INSECURE |
Allows HTTP/private webhook targets for local development | true in Compose |
MAILX_WEBHOOK_WORKERS |
Number of webhook delivery workers | 4 |
Do not commit a real .env file. Use distinct random secrets and restricted network bindings outside local development.
When DATABASE_URL is unset, running MailX starts the original SMTP receiver and local file store without the REST API, PostgreSQL pipeline, Redis queue, or webhooks:
go run ./cmd/mailxThis mode is useful for parser and receiver development; it is not equivalent to the complete stack.
Messages accepted by the SMTP receiver can be listed and inspected from the local file store:
go run ./cmd/mailx list
go run ./cmd/mailx inspect MAILX-IDSet MAILX_STORAGE_ROOT when the messages are not stored in the default location. The inspection command reports metadata and attachment information without rendering HTML or executing remote content.
Start the infrastructure dependencies, then run the full validation suite:
docker compose up -d postgres redis
export MAILX_TEST_DATABASE_URL='postgres://mailx:mailx-dev-only@127.0.0.1:5432/mailx?sslmode=disable'
export REDIS_ADDR='127.0.0.1:6379'
go fmt ./...
go vet ./...
go test -count=1 ./...
go test -race -count=1 ./...
go build ./...
git diff --checkThe test suite includes unit, integration, concurrency, protocol-boundary, persistence, tenant-isolation, retry, and security regression coverage. PostgreSQL and Redis integration tests require those services to be reachable.
Read CONTRIBUTING.md before opening a change. Pull requests should be focused, tested, and use Conventional Commit messages.
MailX is an educational and engineering project under active development. The current codebase implements milestones through durable webhook delivery; operational observability and transport security remain ongoing roadmap work.
The following are important current boundaries:
- No inbound or outbound STARTTLS/TLS yet.
- No SMTP AUTH yet.
- DNS ownership verification does not imply DKIM, SPF, DMARC, or inbox placement.
- API acceptance and SMTP acceptance have distinct meanings.
- Delivery and webhook processing are at-least-once, not exactly-once.
- The supplied Compose credentials and webhook settings are local-development defaults only.
MailX handles private message data and security-sensitive credentials. Do not include real API keys, webhook secrets, SMTP credentials, message bodies, or connection strings in issues or logs.
Report vulnerabilities privately according to SECURITY.md. Do not open a public issue containing exploit details.
- MailX is maintained by Feranmi Oresajo with contributions welcomed from the community.
- Read the contribution guide before submitting code.
- Follow the Code of Conduct in all project spaces.
- Use GitHub Issues for reproducible bugs and scoped feature proposals.
- Use the generated OpenAPI contract as the source of truth for the current HTTP surface.
MailX is available under the MIT License.
