Distributed mass-mailing infrastructure platform. Upload contacts, build templates, launch campaigns, and deliver email at scale through a throttled, retrying worker pool.
Built with:
- Next.js (App Router) — frontend
- Golang — backend API + delivery worker
- PostgreSQL — persistent storage
- Redis — job queue between backend and worker
- MailHog — local SMTP capture
- Docker Compose — local orchestration
- NGINX — reverse proxy
Frontend (Next.js)
│
▼
NGINX Reverse Proxy
/ → frontend
/api/* → backend
│
▼
Backend API (Go) ──────────► PostgreSQL
│ ▲
│ enqueue email jobs │ read/write
▼ │
Redis Queue │
│ │
▼ │
Worker pool (Go) ────────────────┘
throttle → render → send → retry
│
▼
SMTP (MailHog in dev)
The backend persists contacts, templates, and campaigns, then fans out per-recipient jobs onto a Redis queue. The worker consumes jobs with a pool of goroutines, applies a rate limiter, renders the template, sends via SMTP, and retries failures with backoff while updating campaign counters in Postgres.
mailforge/
│
├── frontend/ # Next.js app (dashboard, campaigns, contacts, templates, login)
├── backend/ # Go API service
│ └── internal/
│ ├── campaigns/ # campaign create/list handlers
│ ├── contacts/ # CSV/Excel upload handler
│ ├── templates/ # template CRUD handlers
│ ├── stats/ # aggregate stats endpoint
│ ├── handlers/ # health, shared handlers
│ ├── queue/ # Redis producer
│ ├── database/ # Postgres + migrations
│ └── routes/ # router setup
├── worker/ # Go delivery worker
│ └── internal/
│ ├── queue/ # Redis consumer
│ ├── renderer/ # template rendering
│ ├── sender/ # SMTP delivery
│ ├── throttling/ # send-rate limiter
│ ├── retry/ # retry/backoff logic
│ ├── campaigns/ # campaign counter updates
│ └── database/ # Postgres access
├── nginx/ # reverse proxy config
├── deployments/ # docker-compose.yml
├── .github/ # CI workflows
├── .env.example
└── README.md
| Layer | Technology |
|---|---|
| Frontend | Next.js (App Router), Tailwind CSS |
| Backend | Golang, gorilla/mux |
| Worker | Golang (goroutine pool) |
| Database | PostgreSQL 16 |
| Queue | Redis 7 |
| Mail (dev) | MailHog |
| Reverse Proxy | NGINX |
| Containers | Docker Compose |
| CI | GitHub Actions |
cp .env.example .env
# adjust values as neededcd deployments
docker compose up --buildcd deployments
docker compose down| Service | URL |
|---|---|
| Frontend | http://localhost:3000 |
| Backend API | http://localhost:8080/api/health |
| NGINX Proxy | http://localhost |
| MailHog UI | http://localhost:8025 |
| PostgreSQL | localhost:5432 |
| Redis | localhost:6379 |
Environment variables (see .env.example):
| Variable | Description |
|---|---|
APP_ENV |
development or production |
DATABASE_URL |
PostgreSQL connection string (backend + worker) |
REDIS_ADDR |
Redis address host:port |
JWT_SECRET |
Secret used to sign auth JWTs (backend) |
SMTP_HOST / SMTP_PORT |
Outbound SMTP server (MailHog in dev) |
NEXT_PUBLIC_API_URL |
Base URL the frontend uses to reach the backend |
Built with Go and gorilla/mux. All routes are served under /api.
All endpoints except /api/health, /api/auth/signup and /api/auth/login
require a Authorization: Bearer <token> header. Data is scoped per-user:
each account only sees its own contacts, templates and campaigns.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/health |
Health check |
| POST | /api/auth/signup |
Create an account, returns a token |
| POST | /api/auth/login |
Log in, returns a token |
| GET | /api/auth/me |
Current authenticated user |
| GET | /api/contacts |
List the user's contacts |
| POST | /api/contacts |
Add a single contact |
| POST | /api/contacts/upload |
Upload contacts via CSV/Excel |
| DELETE | /api/contacts/{id} |
Delete a single contact |
| POST | /api/templates |
Create a template |
| GET | /api/templates |
List templates |
| POST | /api/templates/preview |
Render a subject/body with sample values |
| GET | /api/templates/{id} |
Get a template |
| PUT | /api/templates/{id} |
Update a template |
| DELETE | /api/templates/{id} |
Delete a template |
| POST | /api/campaigns |
Create a campaign (enqueues delivery jobs) |
| GET | /api/campaigns |
List campaigns |
| GET | /api/stats |
Aggregate sending stats |
A standalone Go service that drains the Redis queue and delivers mail.
- Worker pool — 20 concurrent goroutines
- Throttling — rate limited to 100 sends/second
- Retries — up to 3 attempts with backoff (base delay 500ms)
- Rendering — fills templates per recipient before sending
- Counters — updates campaign sent/failed counts in Postgres
- Graceful shutdown — drains on SIGTERM/SIGINT
PostgreSQL with versioned migrations in backend/internal/database/migrations:
000001— contacts table000002— templates table000003— campaigns table000004— campaign counters000005— users table000006— per-user ownership (user_id on contacts/templates/campaigns)
Next.js (App Router) with Tailwind CSS. Pages:
- Dashboard
- Campaigns
- Contacts
- Templates
- Login
Reverse proxy routing:
/ → frontend (Next.js)
/api/* → backend (Go API)
All services run via Docker Compose (deployments/docker-compose.yml):
mailforge-frontendmailforge-backendmailforge-workermailforge-postgresmailforge-redismailforge-mailhogmailforge-nginx
GitHub Actions builds and validates the frontend, backend, and worker.
MIT