The Problem

Teams that need programmatic email sending face a binary choice: pay per-volume for SaaS providers like SendGrid or Mailgun, or assemble a fragile pipeline of Postfix, a template engine, and custom analytics. The first option leaks customer data to third parties and scales cost with every message. The second option burns engineering time on infrastructure that isn't your product.

What This Does

Posta is a self-hosted email platform that replaces both of those options. It exposes a REST API for sending transactional, batch, and templated emails, then handles SMTP delivery, inbound receiving, templates, subscriber lists, campaigns, and analytics internally. The Go backend in internal/ manages the core logic, while the web/ directory holds a Vue/TypeScript admin dashboard.

The system is built around three executables in cmd/posta/: main.go for the API server, server.go for SMTP relay and inbound handling, and worker.go for background jobs. It uses Redis and Asynq for async processing, with a PostgreSQL database for persistence. The docs/ directory contains a full Docusaurus site covering installation and every feature.

How It Is Wired

Execution starts at main in cmd/posta/main.go, which bootstraps configuration, database connections, and the HTTP router. The router in internal/routes/routes.go is the central hub—it imports 109 other modules and wires every handler to its endpoint. From there, requests flow into handlers like internal/handlers/api_key_handler.go, which is the most connected file: it touches the database, makes outbound network calls, and reads/writes files.

The call graph shows the system's true backbone. ok is called from 95 places, GetInt from 78, and FindByID from 73—these are the functions where changes ripple widest. The settings provider in internal/services/settings/provider.go is a critical dependency, called from 28 files for configuration lookups.

The system has three clear execution paths:

  • API requests: mainInitRoutes → handler → repository → database
  • Background workers: ProcessTask in internal/worker/handler.go reaches 159 functions and handles email delivery, retries, and webhooks
  • Cron jobs: Run in internal/cron/jobs/account_cleanup.go reaches 139 functions and handles scheduled maintenance

Three modules sit in a circular import cycle: web/src/api/client.ts, web/src/stores/auth.ts, and web/src/api/auth.ts. This makes the frontend auth flow harder to refactor than it should be.

How To Use It

Setup:

git clone https://github.com/moses-y/posta
cd posta
cp .env.example .env
docker compose up -d

Configuration: The .env.example file defines required environment variables for PostgreSQL, Redis, SMTP credentials, and JWT secrets. The compose.yml at the root orchestrates the full stack.

Running it: The Docker image built from docker/Dockerfile is the primary deployment path. For development, the Makefile provides targets for building and testing the Go binaries in cmd/posta/.

Real-World Use

A SaaS company can self-host Posta to send transactional emails—password resets, receipts, notifications—without sending customer data to a third party. The API accepts a POST to /api/v1/emails/send with an API key, returns a queued message ID, and handles delivery, retries, and bounce tracking internally. The built-in SMTP relay in internal/handlers/smtp_handler.go also lets the company receive replies and forward them to support systems via webhooks.

Code Health & Issues

Static analysis found 43 issues: 9 high, 34 medium, 0 low. The high-severity findings are:

  • High - Import cycle in web/src/api/client.ts, web/src/stores/auth.ts, web/src/api/auth.ts—mutually reachable modules that complicate refactoring
  • High - 11 oversized files, including web/src/api/types.ts at 980 lines and internal/routes/workspace_resource_routes.go
  • High - Hub modules: 31 modules depend on web/src/api/types.ts; churn there has high blast radius
  • High - Deep nesting (max depth 8) in web/src/views/domains/Domains.vue and internal/handlers/inbound_handler.go
  • High - 920 duplicated 6-line code blocks across 319 files, notably in cmd/posta/ and internal/config/config.go

The CI workflow pins GitHub Actions to mutable tags instead of commit SHAs, and the Docker base images (node:24-bookworm-slim, golang:1.26-alpine) aren't pinned by digest. No dependency vulnerability scan runs in CI.

The Bottom Line

Posta is a serious, feature-complete email platform with good documentation and a sane Go/Vue architecture. The main risks are the oversized files and duplicated logic in the backend, which will make maintenance harder as the codebase grows. It's a solid choice for teams that want full control over email infrastructure and are willing to operate their own stack.