postalserver/postal
๐ฎ A fully featured open source mail delivery platform for incoming & outgoing e-mail
Healthy across all four use cases
Permissive license, no critical CVEs, actively maintained โ safe to depend on.
Has a license, tests, and CI โ clean foundation to fork and modify.
Documented and popular โ useful reference codebase to read through.
No critical CVEs, sane security posture โ runnable as-is.
- โLast commit 1d ago
- โ7 active contributors
- โMIT licensed
Show 3 more โShow less
- โCI configured
- โTests present
- โ Single-maintainer risk โ top contributor 80% of recent commits
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README โ live-updates from the latest cached analysis.
[](https://repopilot.app/r/postalserver/postal)Paste at the top of your README.md โ renders inline like a shields.io badge.
โธPreview social card (1200ร630)
This card auto-renders when someone shares https://repopilot.app/r/postalserver/postal on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: postalserver/postal
Generated by RepoPilot ยท 2026-05-10 ยท Source
๐คAgent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale โ STOP and ask the user to regenerate it before proceeding. - Treat the AI ยท unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/postalserver/postal shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything โ but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
๐ฏVerdict
GO โ Healthy across all four use cases
- Last commit 1d ago
- 7 active contributors
- MIT licensed
- CI configured
- Tests present
- โ Single-maintainer risk โ top contributor 80% of recent commits
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
โ Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live postalserver/postal
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale โ regenerate it at
repopilot.app/r/postalserver/postal.
What it runs against: a local clone of postalserver/postal โ the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in postalserver/postal | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch main exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit โค 31 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of postalserver/postal. If you don't
# have one yet, run these first:
#
# git clone https://github.com/postalserver/postal.git
# cd postal
#
# Then paste this script. Every check is read-only โ no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of postalserver/postal and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "postalserver/postal(\\.git)?\\b" \\
&& ok "origin remote is postalserver/postal" \\
|| miss "origin remote is not postalserver/postal (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift โ was MIT at generation time"
# 3. Default branch
git rev-parse --verify main >/dev/null 2>&1 \\
&& ok "default branch main exists" \\
|| miss "default branch main no longer exists"
# 4. Critical files exist
test -f "Gemfile" \\
&& ok "Gemfile" \\
|| miss "missing critical file: Gemfile"
test -f "config.ru" \\
&& ok "config.ru" \\
|| miss "missing critical file: config.ru"
test -f "app/models" \\
&& ok "app/models" \\
|| miss "missing critical file: app/models"
test -f "Procfile.dev" \\
&& ok "Procfile.dev" \\
|| miss "missing critical file: Procfile.dev"
test -f ".rubocop.yml" \\
&& ok ".rubocop.yml" \\
|| miss "missing critical file: .rubocop.yml"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 31 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~1d)"
else
miss "last commit was $days_since_last days ago โ artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) โ safe to trust"
else
echo "artifact has $fail stale claim(s) โ regenerate at https://repopilot.app/r/postalserver/postal"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
โกTL;DR
Postal is a complete open-source mail server platform written in Ruby that handles both incoming and outgoing email delivery. It provides a self-hosted alternative to commercial services like Sendgrid, Mailgun, and Postmark, with full SMTP support, message queuing, webhook delivery, and admin dashboard built in Haml/SCSS. Monolithic Rails application structured as app/assets (Haml templates, SCSS styling, embedded fonts), with business logic in app/ following Rails conventions. Configuration via Dockerfile and Procfile.dev indicates containerized deployment. The project uses a standard Rails MVC layout with API endpoints alongside the admin dashboard, all in one codebase.
๐ฅWho it's for
DevOps engineers and system administrators who need to run a private mail delivery infrastructure on their own servers, and developers integrating outgoing email into web applications who want control over their mail infrastructure without vendor lock-in.
๐ฑMaturity & risk
Postal is production-ready with an active codebase (724KB of Ruby code, documented CI/CD in .github/workflows/ci.yml). It has a clear release process (.release-please-manifest.json), comprehensive documentation referenced in README, and a healthy community (Discord, discussions, issue templates). Last activity appears recent with established Procfile.dev and Docker support, though specific commit recency cannot be determined from file list alone.
As a mail server handling critical infrastructure, Postal carries operational complexity (requires database, SMTP daemon, webhook processing). Single-project ownership means feature velocity depends on community contributions. The monolithic Rails architecture means changes to core email handling affect the entire system. Dependency risk is moderate for a mature Ruby project but should be monitored given security-sensitive nature of mail handling.
Active areas of work
The project uses release-please automation (.release-please-manifest.json) for versioned releases with CHANGELOG.md tracking. CI workflows in .github/workflows/ (ci.yml and close.yml) suggest active maintenance. The presence of SECURITY.md and CONTRIBUTING.md indicates organized governance. Specific current PRs/issues cannot be determined from file list, but release automation suggests regular deployments.
๐Get running
git clone https://github.com/postalserver/postal.git
cd postal
bundle install
bundle exec rails db:create db:migrate
bundle exec foreman start -f Procfile.dev
Daily commands:
Development: bundle exec foreman start -f Procfile.dev (uses Procfile.dev for multi-process management). Production: Docker via Dockerfile (supports containerized deployment). Rails console: bundle exec rails console. Database operations: bundle exec rails db:migrate.
๐บ๏ธMap of the codebase
Gemfileโ Declares all Ruby dependencies including Rails framework, mail processing (Mail gem), and background job workers that form the foundation of the mail serverconfig.ruโ Entry point for the Rack application; defines how the Rails app boots and handles incoming HTTP requestsapp/modelsโ Core domain models (Message, Organization, Credential, etc.) that represent mail entities, deliveries, and accountsโthe heart of all business logicProcfile.devโ Defines development processes (web server, background workers, mail receiver daemon) essential for understanding how Postal operates in multi-process mode.rubocop.ymlโ Code style and quality standards that all contributors must follow to maintain consistency across the 600-file codebaseDockerfileโ Production deployment configuration that shows runtime dependencies, initialization sequence, and how Postal services are containerizedCONTRIBUTING.mdโ Outlines development workflow, testing requirements, and code submission standards that every contributor must follow
๐ ๏ธHow to make changes
Add a new Mail Credential Type
- Create a new credential model in app/models extending the base Credential class with vendor-specific authentication logic (
app/models) - Add a controller action in the credentials controller to handle form submission and validation (
app/controllers) - Create a view template in app/views for the credential form with appropriate fields (
app/views) - Add a delivery adapter in the app/models that implements the send_message method for the new credential type (
app/models) - Update the Delivery model to route messages based on the new credential type (
app/models)
Add a new Admin Dashboard Panel
- Create a new controller action in app/controllers for your dashboard panel (
app/controllers) - Create a view template at app/views with your dashboard HTML/ERB markup (
app/views) - Add CSS styles specific to the new panel in app/assets/stylesheets/application/components (
app/assets/stylesheets/application/components) - If interactive, add JavaScript handlers in app/assets/javascripts/application/elements (
app/assets/javascripts/application/elements) - Add route mapping in config/routes.rb to wire the controller action (
config/routes.rb)
Add an Incoming Mail Webhook Event
- Define a new event constant in the Message model or create an event handler class (
app/models) - Trigger the webhook dispatch in the appropriate message processing step (e.g., after SMTP receipt validation) (
app/models) - Create a background job class that executes the webhook HTTP POST to subscribed endpoints (
app/jobs) - Add webhook retry logic and error handling in the background job with exponential backoff (
app/jobs) - Create a webhook delivery log model to track outbound webhook requests and responses (
app/models)
Add a New Settings Configuration Option
- Add a configuration key to the environment or config/postal.yml with a sensible default (
config) - Create a settings model or extend existing Settings to store and validate the configuration (
app/models) - Add a form field in app/views/admin/settings to allow users to modify the option (
app/views) - Add controller logic to save and load the setting with appropriate permissions checks (
app/controllers)
๐งWhy these technologies
- Ruby on Rails โ Provides rapid web development, built-in ORM, routing, and middleware; ideal for building a full-featured mail platform with admin UI and API
- Mail gem + Sinatra SMTP server โ Mail handles RFC-compliant message parsing; Sinatra provides lightweight SMTP server implementation for receiving incoming mail
- Delayed Job / Background queues โ Enables asynchronous processing of mail delivery, webhooks, and retries without blocking HTTP requests
- PostgreSQL / MySQL โ Relational database stores messages, credentials, organizations, delivery logs, and webhook history with strong ACID guarantees
- Docker + Procfile โ Containerized deployment and multi-process management (web, worker, mail receiver) make Postal portable across infrastructure
โ๏ธTrade-offs already made
-
Multi-process architecture (web + worker + mail receiver daemon)
- Why: Allows independent scaling of HTTP API, background jobs, and SMTP reception; simplifies monitoring and restart policies
- Consequence: Requires orchestration layer (Docker, systemd) to manage processes; adds operational complexity vs. single monolithic process
-
In-database job queue (Delayed Job) vs. external queue (Redis, RabbitMQ)
- Why: Reduces external dependencies and operational overhead; leverages existing database connection pool
- Consequence: Database polling adds latency (~5โ30s) compared to event-driven queues; not ideal for very high throughput (>10k msgs/sec)
-
Webhook-first event model instead of direct callbacks
- Why: Decouples Postal from subscriber systems; enables retry logic and audit trails; supports multiple subscribers per event
- Consequence: Adds complexity (delivery logs, retries, exponential backoff); eventual consistency instead of synchronous acknowledgment
-
Rails monolith rather than microservices
- Why: Single codebase easier to maintain and deploy; shared domain knowledge (Message, Organization, Credential) reduces duplication
- Consequence: Harder to scale individual components (e.g., mail receiver) independently; slower deploy cycles if modifying core models
๐ซNon-goals (don't propose these)
- Does not provide mailbox/IMAP storageโfocuses on delivery and ingestion, not long-term mail archival
- Does not handle end-user authentication via OAuth/SAMLโexpects integration with external auth systems via API key
- Not a fully managed SaaS platformโrequires self-hosting on your own servers
- Does not provide advanced spam/virus scanningโrelies on external integrations or downstream filtering
๐ชคTraps & gotchas
SMTP daemon binding: Postal runs an SMTP listener that requires specific port configuration (typically 25, 587); ensure firewall and OS allow binding. Background jobs critical: Message delivery and webhook dispatch happen asynchronously via background workers defined in Procfile.dev; if workers don't run, mail queues indefinitely. Database schema: Rails migrations are mandatory before first run; incomplete migrations will cause cryptic errors. Font files: Custom fonts in app/assets/fonts/ must be served correctly in production; check asset pipeline precompilation. No evident environment variable docs: Look for config/secrets.yml or initializers/ for required ENV setup (database credentials, API keys, SMTP binding address).
๐๏ธArchitecture
๐กConcepts to learn
- SMTP Protocol & Daemon โ Postal implements an SMTP receiver to accept incoming mail from MTAs and client applications; understanding SMTP state machines, envelope vs. header data, and auth mechanisms is essential to mail-server development
- Message Queue / Background Job Processing โ Postal defers email delivery and webhook dispatch to asynchronous workers to avoid blocking web requests; this requires understanding job serialization, retry logic, and worker process management
- DNS MX / SPF / DKIM Records โ For Postal to reliably deliver mail and avoid spam filters, operators must configure DNS records correctly; understanding these authentication mechanisms is non-trivial for contributors managing mail deliverability
- Rails Asset Pipeline โ Custom fonts, icons, and stylesheets in app/assets/ require proper precompilation and serving in production; misconfiguration breaks the admin dashboard UI
- Webhook Delivery & Retry Logic โ Postal notifies external systems about mail events via webhooks; implementing reliable delivery with exponential backoff and idempotency is a core feature and common failure point
- Docker Image Optimization & Multi-stage Builds โ Postal's Dockerfile must efficiently package Ruby, dependencies, and assets for production; understanding layer caching and minimal base images affects deployment size and startup time
- Rate Limiting & Quota Management โ Mail servers must enforce sending limits per user/organization to prevent abuse; Postal likely implements token-bucket or sliding-window rate limiting in the API layer
๐Related repos
mailcatcher/mailcatcherโ Lightweight local SMTP/web interface for development email testing; complements Postal for test environmentsdiscourse/discourseโ Large Ruby on Rails monolith that also manages complex email workflows; good reference for scaling Rails email architectureminio/minioโ Self-hosted alternative to cloud services (like S3); relevant for organizations wanting Postal + private object storagepostalserver/postal-nodeโ Official Node.js client SDK for Postal API; essential for non-Ruby applications integrating with Postalpostalserver/postal-pythonโ Official Python client SDK for Postal API; extends Postal ecosystem to Python-based applications
๐ชPR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add comprehensive CI workflow for Ruby linting and security scanning
The repo has .rubocop.yml config file but the CI workflow (.github/workflows/ci.yml) likely lacks automated Ruby code quality checks. This is critical for a production mail server where code quality and security directly impact email delivery reliability. Adding RuboCop linting, Brakeman security scanning, and bundle-audit checks would catch issues early.
- [ ] Review current .github/workflows/ci.yml to identify missing Ruby quality checks
- [ ] Add RuboCop linting step that runs against .rubocop.yml configuration
- [ ] Integrate Brakeman security scanner for Rails vulnerability detection
- [ ] Add bundle-audit step to scan Gemfile.lock for known vulnerabilities
- [ ] Configure workflow to fail on security issues but warn on style violations
Add missing asset optimization and precompilation tests
The repo contains numerous font files (.eot, .ttf, .woff) and SVG icons in app/assets, but there's no documented validation that assets are properly compiled and optimized in CI. For a production mail server UI, broken assets or missing font fallbacks could degrade the admin experience. Adding asset pipeline tests ensures consistency across deployments.
- [ ] Create new spec file: spec/assets_spec.rb or similar
- [ ] Add test to verify all font files in app/assets/fonts compile without Rails asset pipeline errors
- [ ] Add test to validate SVG icons in app/assets/images/icons are properly referenced and not broken
- [ ] Add test to verify app/assets/config/manifest.js includes all critical assets
- [ ] Integrate asset compilation check into .github/workflows/ci.yml
Create security policy and vulnerability disclosure workflow documentation
The repo has SECURITY.md file but based on the file structure, there's likely missing or incomplete security vulnerability disclosure procedures and responsible disclosure guidelines. As an open-source mail server handling sensitive email data, contributors need clear guidance on reporting security issues safely. This protects both the project and users.
- [ ] Review current SECURITY.md for completeness against GitHub security policy best practices
- [ ] Add specific security contact information and PGP key for encrypted disclosures if not present
- [ ] Document the vulnerability disclosure timeline and process in SECURITY.md
- [ ] Create .github/SECURITY.md as alternative location if it doesn't exist with identical content
- [ ] Add security vulnerability reporting template similar to existing bug_report.md in .github/ISSUE_TEMPLATE/
- [ ] Document any known security limitations or audit requirements for self-hosted deployments
๐ฟGood first issues
- Add comprehensive test coverage for app/controllers/ using RSpec; the CI pipeline exists (.github/workflows/ci.yml) but controller specs are likely sparse given no test/ directory visible
- Document the exact configuration steps for Postal in a standalone config-setup.md guide with example .env file and Dockerfile customization (docs/ might exist externally but not in repo)
- Add missing i18n translations to Haml templates in app/views/; currently hardcoded English strings could block non-English deployments
โญTop contributors
Click to expand
Top contributors
- @adamcooke โ 80 commits
- @github-actions[bot] โ 15 commits
- @melledouwsma โ 1 commits
- @johankok โ 1 commits
- @arthurzenika โ 1 commits
๐Recent commits
Click to expand
Recent commits
8ef8960โ chore(main): release 3.3.6 (#3558) (github-actions[bot])84f4e20โ refactor(auth): tighten return_to validation (adamcooke)9243524โ refactor(helpers): escape interpolated values in select options (adamcooke)dca7f90โ refactor(tracking): remove unused src image proxy (adamcooke)cad2aa6โ fix(messages): sandbox rendered email HTML as extra XSS defence (adamcooke)b611d57โ chore: ignore node modules and yarn.lock (adamcooke)d532922โ chore(main): release 3.3.5 (#3208) (github-actions[bot])11419f9โ fix(deliveries): escape delivery details to prevent HTML injection (adamcooke)b7e5232โ fix: typo in process logging (#3212) (melledouwsma)e00098bโ fix: update url for v2 config (#3225) (johankok)
๐Security observations
- High ยท Insecure Node.js Setup Script Installation โ
Dockerfile, line: RUN (curl -sL https://deb.nodesource.com/setup_20.x | bash -). The Dockerfile uses curl to download and execute a setup script from deb.nodesource.com without verification. This is vulnerable to man-in-the-middle attacks and compromised repository scripts. The script is piped directly to bash without checksum verification. Fix: Use official Node.js packages from Debian repositories or verify script integrity with checksums. Consider: 'apt-get install -y nodejs' from standard repositories or use a verified package manager approach. - High ยท Database Credentials Hardcoded in Docker Compose โ
docker-compose.yml, MariaDB service configuration. The docker-compose.yml file specifies database credentials as environment variables without using secrets management. Additionally, empty root passwords are explicitly allowed (MARIADB_ALLOW_EMPTY_ROOT_PASSWORD='yes'), which is a critical security anti-pattern. Fix: Use Docker secrets or environment files (.env) with proper access controls. Never allow empty root passwords in production. Implement strong credentials and use Docker secrets: 'docker secret create' or similar secret management. - High ยท Setcap Usage Without Verification โ
Dockerfile, line: RUN setcap 'cap_net_bind_service=+ep' /usr/local/bin/ruby. The Dockerfile uses setcap to allow the Ruby binary to bind to privileged ports (<1024). While necessary for port binding, this grants significant privileges to the Ruby process and could be exploited if the Ruby process is compromised. Fix: Run the application on non-privileged ports (>1024) and use a reverse proxy (nginx, HAProxy) to bind to privileged ports. If capability is necessary, restrict it further and ensure the application runs with minimal privileges otherwise. - Medium ยท Incomplete Dockerfile (Truncated Build Instructions) โ
Dockerfile, end of file. The Dockerfile appears incomplete with the comment '# Install the latest and ac' suggesting truncated or missing build steps. This may hide important security steps or create unpredictable build behavior. Fix: Complete all build instructions and ensure the Dockerfile is fully reviewed. Add multi-stage builds to minimize final image size and attack surface. - Medium ยท Missing Security Headers Configuration โ
app/assets/config/manifest.js and overall Rails configuration. No security headers (Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, etc.) are evident in the Rails asset configuration or application setup provided. Fix: Implement Rails security headers middleware. Add secure defaults in config/initializers/security_headers.rb using gems like 'secure_headers' or configure in Rails.application.config. - Medium ยท Non-Root User Privilege Elevation Risk โ
Dockerfile, USER directive and setcap usage. While the Dockerfile creates a non-root 'postal' user (UID 999), the setcap on Ruby binary could allow privilege escalation if combined with other vulnerabilities. Fix: Use a reverse proxy running as root to bind to privileged ports, then forward to the application running as non-root. Alternatively, use systemd socket activation or port forwarding at the container orchestration level. - Medium ยท Bundler Version Pinning Without Verification โ
Dockerfile, line: RUN gem install bundler -v 2.7.2 --no-doc. The Dockerfile installs a specific bundler version (2.7.2) via gem install without checksum verification or source pinning. Fix: Verify bundler checksums or use a more recent version. Consider pinning the Ruby and bundler versions in a .ruby-gemset or use rbenv/asdf with checksums. - Low ยท Missing HEALTHCHECK in Dockerfile โ
Dockerfile. The Dockerfile lacks a HEALTHCHECK instruction, making it difficult to detect when the container becomes unhealthy. Fix: Add a HEALTHCHECK instruction to monitor application availability, e.g., 'HEALTHCHECK CMD curl -f http://localhost/health || exit 1'. - Low ยท Apt-get Cache Not Fully Cleaned โ
undefined. undefined Fix: undefined
LLM-derived; treat as a starting point, not a security audit.
๐Where to read next
- Open issues โ current backlog
- Recent PRs โ what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals โ see the live page for receipts. Re-run on a new commit to refresh.