Posthawk
TutorialFeb 4, 20258 min read

Self-Hosting Email: A Complete Guide Using Docker

Self-hosting your email infrastructure gives you full control over your data, eliminates per-email fees, and lets you customize every aspect of the delivery pipeline. In this guide, we'll walk through deploying Posthawk on your own server using Docker Compose.

Prerequisites

  • A server with Docker and Docker Compose installed (any VPS with 2GB+ RAM)
  • An AWS account with SES configured (even the sandbox is fine to start)
  • A domain name you own for sending emails
  • Basic terminal/command-line knowledge

Step 1: Clone and configure

Start by cloning the Posthawk repository and setting up your environment variables. The .env.example file contains all the configuration options you need.

bash
git clone https://github.com/Baftjar/posthawk.git
cd posthawk
cp .env.example .env

Open the .env file and fill in your AWS SES credentials. You'll need your AWS access key, secret key, and the SES region you've configured.

bash
# .env
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_REGION=us-east-1
SES_FROM_EMAIL=noreply@yourdomain.com

# Supabase (included in Docker Compose)
SUPABASE_URL=http://localhost:8000
SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_KEY=your-service-key

# Redis (included in Docker Compose)
REDIS_URL=redis://localhost:6379

Step 2: Start the services

Posthawk ships with a Docker Compose file that includes everything: the web dashboard, the worker process, Redis for job queuing, and PostgreSQL via Supabase. One command to start it all.

bash
docker compose up -d

This starts four services: the Next.js web app on port 3000, the NestJS worker on port 3001, Redis on port 6379, and Supabase (PostgreSQL + Auth) on port 8000. Give it about 30 seconds for everything to initialize.

Step 3: Configure your domain

For emails to actually reach inboxes, you need to verify your domain with SES and configure DNS records. Posthawk's dashboard walks you through this, but here's what you'll need to add:

  • SPF record: Authorizes SES to send emails from your domain
  • DKIM record: Cryptographically signs your emails to prevent spoofing
  • DMARC record: Tells receiving servers how to handle failed authentication
  • MX record (optional): Required only if you want to receive inbound emails

Step 4: Send your first email

Once your domain is verified, you can send your first email through the API. Generate an API key from the dashboard, then use curl or any HTTP client.

bash
curl -X POST http://localhost:3001/api/v1/emails/send \
  -H "x-api-key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "test@example.com",
    "subject": "Hello from Posthawk!",
    "html": "<h1>It works!</h1><p>Your self-hosted email is ready.</p>"
  }'

Check the dashboard at http://localhost:3000 to see the email in your logs. You'll see the full lifecycle: queued, sent, delivered (or bounced). Every email is tracked with timestamps and SES message IDs.

Monitoring and maintenance

Posthawk logs everything to stdout, so you can use any log aggregation tool (Datadog, Grafana Loki, even just Docker logs). The dashboard gives you real-time stats on delivery rates, bounces, and queue depth. For production deployments, we recommend setting up health checks and alerting on the /health endpoint.

That's it. You now have a fully self-hosted email infrastructure running on your own server. No per-email fees, no vendor lock-in, and complete control over your data. Welcome to email freedom.

Next article
Guide

Building Beautiful Templates with React Email