Skip to main content

AI & assistant-friendly summary

This section provides structured content for AI assistants and search engines. You can cite or summarize it when referencing this page.

Summary

Postal v3 on AWS: 2 vCPU / 4 GiB minimum per official docs, RDS MariaDB production path, $250-$700/mo dev footprint, Docker + Caddy architecture, and 100+ item readiness checklist.

Key Facts

  • Postal v3 on AWS: 2 vCPU / 4 GiB minimum per official docs, RDS MariaDB production path, $250-$700/mo dev footprint, Docker + Caddy architecture, and 100+ item readiness checklist
  • Postal v3's official prerequisites specify a dedicated server with at least 2 CPU cores, 4 GiB RAM, and 25 GiB disk, running Docker and MariaDB 10
  • 6+
  • AWS documents the same EC2 port-25 restriction that affects every self-hosted mail platform
  • As of July 2026, Postal v3 runs as containerized web, SMTP, and worker services via the postalserver/install helper — not as a single monolithic daemon

Entity Definitions

SES
SES is an AWS service discussed in this article.
Amazon SES
Amazon SES is an AWS service discussed in this article.
EC2
EC2 is an AWS service discussed in this article.
S3
S3 is an AWS service discussed in this article.
RDS
RDS is an AWS service discussed in this article.
CloudFront
CloudFront is an AWS service discussed in this article.
CloudWatch
CloudWatch is an AWS service discussed in this article.
IAM
IAM is an AWS service discussed in this article.

How to Set Up Postal v3 on AWS for Production: The Ultimate Enterprise Guide

Email DeliverabilityPalaniappan P16 min read

Quick summary: Postal v3 on AWS: 2 vCPU / 4 GiB minimum per official docs, RDS MariaDB production path, $250-$700/mo dev footprint, Docker + Caddy architecture, and 100+ item readiness checklist.

Key Takeaways

  • Postal v3 on AWS: 2 vCPU / 4 GiB minimum per official docs, RDS MariaDB production path, $250-$700/mo dev footprint, Docker + Caddy architecture, and 100+ item readiness checklist
  • Postal v3's official prerequisites specify a dedicated server with at least 2 CPU cores, 4 GiB RAM, and 25 GiB disk, running Docker and MariaDB 10
  • 6+
  • AWS documents the same EC2 port-25 restriction that affects every self-hosted mail platform
  • As of July 2026, Postal v3 runs as containerized web, SMTP, and worker services via the postalserver/install helper — not as a single monolithic daemon
How to Set Up Postal v3 on AWS for Production: The Ultimate Enterprise Guide
Table of Contents

Postal v3’s official prerequisites specify a dedicated server with at least 2 CPU cores, 4 GiB RAM, and 25 GiB disk, running Docker and MariaDB 10.6+. AWS documents the same EC2 port-25 restriction that affects every self-hosted mail platform. As of July 2026, Postal v3 runs as containerized web, SMTP, and worker services via the postalserver/install helper — not as a single monolithic daemon.

This guide is for DevOps engineers, platform teams, and deliverability specialists deploying Postal v3 on AWS. For managed paths, see Amazon SES consulting and SES migration. Comparing bare MTA options? Read our KumoMTA on AWS guide.

Artifacts: README, postal.yml example, Route53 DNS template, CloudWatch agent config, readiness checklist.

Benchmark pattern (not a cited client) — B2B SaaS notification platform, 50k-200k emails/day, t3.xlarge EC2 + RDS db.t4g.medium MariaDB, HTTP API primary send path. Development AWS footprint: $250-$700/month (compute + RDS + observability in this guide’s cost table).

What broke — A team used the dev docker run mariadb example from Postal docs with the default password and innodb_log_file_size left at RDS defaults. Messages above 8 MB failed silently in the worker queue until log inspection revealed InnoDB row-size errors. Fix: RDS parameter group with 160 MB innodb_log_file_size for a 15 MB max message policy.

Reproduce this — Copy templates from examples/postal-aws-production/. Run postal bootstrap on a non-production hostname before publishing customer DNS.

Executive Summary

Postal v3 on AWS is a containerized mail delivery platform — web UI, HTTP API, SMTP server, and background workers — backed by MariaDB. It fits teams that want self-hosted email with a management interface, not teams that only need a high-throughput MTA engine.

QuestionShort answer
Who needs Postal on AWS?SaaS and agencies sending 50k+ emails/day who want UI-managed virtual mail servers and HTTP API injection
Who should skip?Teams without deliverability ops capacity, or senders better served by Amazon SES
Minimum shape?2 vCPU / 4 GiB per Postal docs; production baseline 4 vCPU / 16 GiB + RDS MariaDB
Biggest architecture mistake?Putting SMTP behind ALB/CloudFront — Postal v3 uses network_mode: host for SMTP and web processes
Our recommendationUse Postal for UI + API + multi-mail-server management; use KumoMTA for policy-heavy high-volume MTA; use SES when managed deliverability wins

What Is Postal v3?

Postal is an open-source mail delivery platform — not just an SMTP relay. Postal v3 packages:

ComponentRoleProduction note
Web serverAdmin UI, HTTP APIBind behind Caddy TLS on postal.example.com
SMTP serverInbound/outbound SMTPnetwork_mode: host; requires Elastic IP + PTR
WorkerQueue processing, delivery attemptsScale vertically before adding second host
MariaDBConfig + message metadataUse RDS Multi-AZ for production
CaddyTLS termination (default install path)--network host per installation docs

Virtual mail servers

Each mail server in Postal is a logical sending entity with its own domains, credentials, IP pools, and statistics. This maps well to SaaS products that send on behalf of multiple customers — but reputation isolation still requires disciplined IP and domain strategy.

Functional model

flowchart LR
  app[SendingApp] --> api[PostalHTTPAPI]
  app --> smtp587[SMTPSubmission]
  api --> worker[PostalWorker]
  smtp587 --> worker
  worker --> smtp25[OutboundSMTP]
  smtp25 --> mx[RecipientMX]
  worker --> db[(MariaDBRDS)]
  worker --> webhooks[WebhooksAndEvents]

Why Choose Postal v3?

AdvantageWhy it matters on AWS
Web UI + APIFaster onboarding than raw MTA configuration
Docker-nativepostal start orchestrates web/smtp/worker containers
Virtual mail serversMulti-tenant sending from one platform install
Open sourceNo per-message license; you pay AWS + operations time
IP poolspostal.use_ip_pools for stream isolation when configured

Feature comparison

PlatformUIHTTP APISMTPMulti-tenantHigh-volume policyOps burden
Postal v3StrongYesYesVirtual mail serversModerateMedium-high
KumoMTAMinimalPolicy hooksYesVia policyStrongHigh
Amazon SESAWS ConsoleYesYesTenants (2025+)ManagedLow-medium
Mailgun/SendGridSaaSYesYesAccountsManagedLow
PostfixNoneNoYesCustomCustomMedium

Recommendation matrix

ScenarioRecommended path
SMB/mid-market self-hosted with UI + APIPostal v3
Engineering-led 1M+ emails/day, Lua shapingKumoMTA
AWS-native managed deliverabilityAmazon SES
Quick ESP migrationSES migration

AWS Production Architecture

CloudFront, WAF, and ALB do not terminate Postal SMTP traffic. Postal v3 containers use network_mode: host for web and SMTP processes (docker-compose.v3.yml). HTTPS is handled by Caddy in the default install path.

flowchart TD
  internet[Internet] --> r53[Route53]
  app[SendingApplications] --> api[PostalHTTPSAPI]
  app --> smtp587[PostalSMTP587]
  api --> ec2[EC2UbuntuDockerHost]
  smtp587 --> ec2
  ec2 --> eip[ElasticIP]
  eip --> mx[RecipientMX]
  ec2 --> caddy[CaddyHostNetworkTLS]
  ec2 --> rds[RDSMariaDB106]
  subgraph support [AWSSupportingServices]
    cw[CloudWatch]
    ssm[SystemsManager]
    sm[SecretsManager]
    s3[S3Backups]
    gd[GuardDuty]
  end
  ec2 --> cw
  ec2 --> ssm
  rds --> sm

Why each AWS service exists

ServiceRole
EC2Docker host for Postal web, smtp, worker, Caddy
Elastic IPStable SMTP/IP reputation identity
RDS MariaDB 10.6+Production database (replaces dev Docker MariaDB)
Route53Postal DNS spec (spf, return-path, route, track domains)
Secrets ManagerDB passwords, API secrets
CloudWatchMetrics, logs, alarms
Systems ManagerPatch and session access without public SSH
S3Config and backup archive
GuardDuty / InspectorThreat and vulnerability visibility

Infrastructure Design

flowchart LR
  igw[InternetGateway] --> pub[PublicSubnet]
  pub --> ec2[PostalEC2]
  pub --> eip[EIP]
  priv[PrivateSubnet] --> rds[RDSMariaDB]
  ec2 --> rds

VPC baseline

ComponentExampleNotes
VPC10.50.0.0/16Room for HA expansion
Public subnet10.50.10.0/24EC2 + Elastic IP
Private subnet A/B10.50.20.0/24, 10.50.21.0/24RDS subnet group
Security group (EC2)In: 80,443,25,587 from required sources; Out: all for SMTP deliveryRestrict 22; use SSM
Security group (RDS)In: 3306 from EC2 SG onlyNo public RDS

Reverse DNS

Request PTR for each Elastic IP so postal.example.com forward and reverse DNS align — same requirement as any production MTA.

Server Sizing

Postal minimum: 2 vCPU, 4 GiB RAM, 25 GiB disk. Production baselines below account for Docker overhead, MariaDB client load, and queue spikes.

TierEC2RDSEst. emails/dayNotes
Developmentt3.mediumdb.t4g.micro< 10kLab only; not for production mail
Startupt3.xlargedb.t4g.medium50k-200kSingle AZ acceptable initially
Mediumm7i.xlargedb.r7g.large200k-1MEnable RDS Multi-AZ
Enterprisem7i.2xlarge+ multi-nodedb.r7g.xlarge Multi-AZ1M+Active-passive Postal hosts + shared RDS

Operating System

Use Ubuntu 22.04 LTS — the official v3 prerequisite script is install-ubuntu.v3.sh (install repo). Install Docker Engine and Docker Compose plugin per Docker docs before Postal bootstrap.

Step-by-Step Installation on AWS

Context: Ubuntu 22.04 LTS EC2, Elastic IP attached, Route53 hostname ready, RDS MariaDB 10.6+ provisioned in private subnets.

0) Provision RDS MariaDB first

Create an RDS MariaDB 10.6+ instance in private subnets before postal initialize — Postal needs credentials that can create and manage postal and postal-* message databases (prerequisites).

SettingProduction value
EngineMariaDB 10.6.20+ or 10.11
Instance classdb.t4g.medium minimum for startup
Storagegp3, autoscaling enabled
Multi-AZYes (production)
Parameter groupinnodb_log_file_size per max message size
Public accessNo
Security groupInbound 3306 from Postal EC2 SG only

Store the master password in Secrets Manager; reference it when editing postal.yml — never commit credentials to git.

1) System packages

sudo apt update && sudo apt upgrade -y
sudo apt install -y git curl jq

2) Docker (official)

# Context: Docker Engine + Compose plugin — see https://docs.docker.com/engine/install/ubuntu/
curl -fsSL https://get.docker.com | sudo sh
sudo docker version && sudo docker compose version

3) Clone Postal v3 install helper

sudo git clone https://github.com/postalserver/install /opt/postal/install
sudo ln -sf /opt/postal/install/bin/postal /usr/bin/postal

4) Bootstrap configuration

# Context: replace postal.example.com with your Route53 hostname
sudo postal bootstrap postal.example.com

This generates /opt/postal/config/postal.yml, signing.key, and Caddyfile.

5) Configure RDS in postal.yml

Edit /opt/postal/config/postal.yml — use v2 format (version: 2). Point main_db and message_db at RDS. See postal.yml.example.

main_db:
  host: postal-db.xxxx.us-east-1.rds.amazonaws.com
  username: postal_admin
  password: '<from-secrets-manager>'
  database: postal

6) Initialize database and admin user

sudo postal initialize
sudo postal make-user

7) Start Postal

sudo postal start
sudo postal status

8) Start Caddy for TLS

# Context: from Postal installation docs — Caddy uses host network
sudo docker run -d \
  --name postal-caddy \
  --restart always \
  --network host \
  -v /opt/postal/config/Caddyfile:/etc/caddy/Caddyfile \
  -v /opt/postal/caddy-data:/data \
  caddy

9) Verify

  • Browse https://postal.example.com and log in
  • Create first virtual mail server in UI
  • Send test via API (see below)

Configure Postal for Production

DNS block in postal.yml

Per DNS configuration docs:

dns:
  mx_records:
    - mx1.postal.example.com
    - mx2.postal.example.com
  spf_include: spf.postal.example.com
  return_path_domain: rp.postal.example.com
  route_domain: routes.postal.example.com
  track_domain: track.postal.example.com

IP pools

Enable postal.use_ip_pools: true and assign pools per mail server in the UI. Separate transactional and marketing streams onto different pools before scaling volume.

HTTP API send example

Context: API key from Postal mail server credentials page

curl --location 'https://postal.example.com/api/v1/send/message' \
  --header 'X-Server-API-Key: YOUR_SERVER_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "to": ["user@example.com"],
    "from": "notifications@yourdomain.com",
    "subject": "Test",
    "plain_body": "Postal v3 on AWS test"
  }'

RDS parameter: innodb_log_file_size

Postal requires innodb_log_file_size10× max message size. For 15 MB messages, set 160 MB or higher in your RDS parameter group before production traffic.

Route53 DNS Configuration

Publish records from route53-records.example.txt:

RecordPurpose
postal.example.com AWeb, API, SMTP host
spf.postal.example.com TXTGlobal SPF include for server IPs
rp.postal.example.comReturn-path / MAIL FROM domain
routes.postal.example.com MXInbound route handling
track.postal.example.com AClick/open tracking (optional)
Customer domain SPFinclude:spf.postal.example.com
DMARC on customer domainsStart p=none, tighten after monitoring

Run postal default-dkim-record for DKIM TXT on the return-path domain.

Security Best Practices

  • SSM Session Manager instead of public SSH
  • Secrets Manager for RDS passwords — inject at boot via user-data or SSM Parameter Store
  • IAM instance role least privilege: CloudWatch, S3 backup bucket, Secrets read
  • Security groups — no 3306 from internet; restrict SMTP submission sources
  • signing.key permissions 600, root-owned under /opt/postal/config
  • Patch cadence — Ubuntu unattended upgrades + Docker image updates via postal upgrade workflow
  • GuardDuty + Inspector on EC2

SSH hardening

# Context: /etc/ssh/sshd_config on Postal EC2
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes

Monitoring Architecture

flowchart LR
  ec2[PostalEC2] --> cwa[CloudWatchAgent]
  cwa --> cwm[Metrics]
  cwa --> cwl[Logs]
  cwm --> alarms[Alarms]
  alarms --> sns[SNS]
  rds[RDS] --> rdsi[RDSMetrics]
  rdsi --> alarms

Priority alarms

SignalThreshold idea
EC2 CPU> 80% for 15 min
Disk usage> 85%
RDS CPU / connectionsSustained high
SMTP 4xx/5xx rateSpike vs baseline
API 5xx from CaddyAny sustained

Deploy cloudwatch-agent.json on the EC2 host.

Logging Pipeline

flowchart TD
  docker[DockerContainers] --> syslog[HostSyslog]
  syslog --> cwl[CloudWatchLogs]
  cwl --> s3[S3Archive]
  s3 --> athena[Athena]
  • Postal logging.stdout: true in config for container log capture
  • Retention: 14-30 days CloudWatch hot, 90-365 days S3 cold

Logrotate on EC2

# Context: /etc/logrotate.d/postal-host — host-level logs alongside Docker
/var/log/syslog {
  daily
  rotate 14
  compress
  delaycompress
  missingok
  notifempty
}

Docker container logs under /var/lib/docker/containers/ grow quickly under high volume — monitor disk and set CloudWatch agent inclusion carefully to control ingest cost.

Email Deliverability

flowchart LR
  dns[DNSAuth] --> warmup[IPWarmup]
  warmup --> send[ConservativeVolume]
  send --> monitor[PostmasterAndComplaints]
  monitor --> tune[PoolAndCadenceTuning]
  • Warm up each new Elastic IP gradually
  • Keep transactional and marketing on separate IP pools
  • Monitor Google Postmaster, Microsoft SNDS, complaint rates in Postal UI
  • Enforce SPF, DKIM (Postal-generated), DMARC on customer domains
  • List-Unsubscribe headers on bulk streams

High Availability

PatternDescription
Single nodeDefault; acceptable for startup with RDS Multi-AZ
Active-passiveSecond EC2 with shared RDS; manual or DNS failover
RDS Multi-AZDatabase failover without Postal config loss

Backup /opt/postal/config (including signing.key) to S3 versioned bucket daily.

flowchart LR
  fail[NodeFailure] --> dns[UpdateDNSorEIP]
  dns --> standby[StandbyPostalHost]
  standby --> rds[RDSMultiAZ]
  standby --> restore[RestoreConfigFromS3]

Performance Tuning

EC2 and Docker

  • EBS gp3 with provisioned IOPS for /var/lib/docker
  • Adequate inode headroom on Docker volume
  • postal status — ensure worker container healthy under load

sysctl baseline

# Context: /etc/sysctl.d/99-postal.conf
net.core.somaxconn = 4096
net.ipv4.ip_local_port_range = 10240 65535
fs.file-max = 2097152

Cost Optimization

ProfileApprox monthly total
Development$250-$700
Startup production$900-$3,000
Medium scale$3,500-$12,000
Enterprise$15,000+

Savings: RDS Reserved Instances, Graviton (t4g/m7g) where tested, S3 lifecycle for logs, right-size EC2 after 30-day baseline.

Troubleshooting

IssueLikely causeFix
postal initialize DB errorRDS SG or credentialsOpen 3306 from EC2 SG; verify Secrets Manager values
Caddy cert failureDNS not pointing to EIPFix Route53 A record; wait for propagation
API 401Wrong API keyRegenerate credential in mail server UI
SMTP timeout outboundPort 25 blockedAWS support request to remove restriction
Large message failuresinnodb_log_file_sizeIncrease RDS parameter per Postal docs
Worker queue stuckWorker container downpostal status; postal restart
Spam folder placementWarmup/reputationReduce volume; check Postmaster
sequenceDiagram
  participant App
  participant Postal as PostalAPI
  participant Worker
  participant MX as RecipientMX
  App->>Postal: POST /api/v1/send/message
  Postal->>Worker: Queue message
  Worker->>MX: SMTP delivery
  MX-->>Worker: 250/4xx/5xx
  Worker-->>App: Webhook event

Maintenance Cadence

CadenceTask
Dailypostal status, queue depth, bounce/complaint review
WeeklyDNS spot-check, cert expiry, RDS storage
MonthlyPatch OS, review RDS performance insights
QuarterlyDR restore drill, config backup test

Do’s and Don’ts

Do’s (30)

#DoWhy
1Use RDS MariaDB for productionSecure, backed-up database
2Run Postal on a dedicated EC2 instancePostal docs requirement
3Request AWS port 25 removal if neededDirect MX delivery
4Align PTR with postal hostnameDeliverability trust
5Publish full Postal DNS setSPF include, return-path, routes
6Use IP pools per streamReputation isolation
7Store secrets in Secrets ManagerNo plaintext in git
8Enable RDS Multi-AZ for productionDB resilience
9Size innodb_log_file_size correctlyLarge message support
10Use SSM for accessReduce SSH attack surface
11Monitor worker container healthQueue processing depends on it
12Backup signing.key securelySigning integrity
13Start DMARC at p=noneVisibility before enforcement
14Warm up new IPs graduallyAvoid ISP throttling
15Test API sends before SMTP cutoverFaster debugging
16Tag AWS resourcesCost allocation
17Enable VPC flow logsNetwork debugging
18Rotate API keys periodicallyCredential hygiene
19Document virtual mail server ownershipMulti-tenant clarity
20Use CloudWatch alarmsEarly incident detection
21Keep Docker images updatedSecurity patches
22Restrict SMTP submission by SGAbuse prevention
23Validate Caddy TLS renewalWeb UI availability
24Archive logs to S3Cost-effective retention
25Run quarterly DR drillsProve restore path
26Separate marketing and transactional poolsProtect OTP mail
27Verify customer domain DNS in UIPer-domain authentication
28Use Elastic IP consistentlyStable reputation
29Review Postal upgrade notes before upgradingv3 breaking changes
30Compare TCO vs SES at your volumeEconomic sanity check

Don’ts (30)

#Don’tWhy
1Don’t use install-ubuntu.v3.sh passwords in prodInsecure defaults
2Don’t put SMTP behind ALBWrong layer for SMTP
3Don’t expose RDS publiclyAttack surface
4Don’t skip PTR setupProvider trust failures
5Don’t share one IP pool for all streamsReputation coupling
6Don’t run other heavy workloads on Postal EC2Resource contention
7Don’t ignore worker container crashesSilent queue stall
8Don’t commit signing.key to gitKey compromise
9Don’t ramp volume without warmupBlocking risk
10Don’t use MySQL instead of MariaDBUnsupported by Postal
11Don’t mix v2 Postal install pathsv3 only in this guide
12Don’t leave default RDS parameters for large mailMessage size failures
13Don’t open SSH to 0.0.0.0/0Brute force risk
14Don’t skip DMARC monitoringBlind spoofing visibility
15Don’t delete mail server DBs manuallyData loss
16Don’t run without backupsNo restore path
17Don’t ignore complaint spikesReputation damage
18Don’t use self-signed certs in productionClient trust issues
19Don’t overload track domain without testingBroken click tracking
20Don’t assume API covers all admin tasksSome functions UI-only today
21Don’t scale EC2 before fixing DB bottlenecksWasted spend
22Don’t forget to restart after config changesPostal docs requirement
23Don’t use single-AZ RDS for production SLADB outage = full stop
24Don’t send marketing from transactional poolOTP placement risk
25Don’t ignore Docker disk growthFull disk stops containers
26Don’t skip synthetic monitoringLate outage detection
27Don’t change SPF without testingAuth failures
28Don’t run production without alarmsOperational blindness
29Don’t treat Postal as zero-ops ESPYou own deliverability
30Don’t block port 25 testing in staging onlyProduction surprise

Common Real-World Mistakes (25)

#ProblemImpactSolution
1Dev MariaDB in productionSecurity + scale limitsRDS MariaDB 10.6+
2Missing innodb_log_file_sizeLarge sends failRDS parameter group
3No PTR on Elastic IPDeferrals/rejectionsAWS PTR request
4Wrong postal.yml pathsContainer mount errorsUse /config paths in containers
5Forgot postal restart after configStale behaviorRestart per docs
6Caddy not on host networkTLS/route failures--network host
7API key on wrong mail server401 errorsMatch server credential
8Marketing on transactional IPOTP spam-folderIP pool split
9No RDS backupsData loss riskEnable automated backup
10Port 25 still blockedSMTP timeoutsAWS support ticket
11Undersized disk for DockerContainer exitMonitor disk/inodes
12Missing return-path DNSBounce handling breaksPublish rp.* records
13No worker health checkQueue backlogMonitor postal status
14Shared signing.key across envsCross-env forgery riskPer-environment keys
15Skipping postal initializeSchema missingRun before start
16Public admin without WAFAbuse scansRestrict or add WAF on 443
17No customer DMARCSpoofing visibility gapPublish _dmarc
18Tracking domain without A recordBroken linkstrack.* A record
19Single admin userNo break-glass backupSecond admin account
20Ignoring Postal upgrade docsBreaking upgradesRead release notes
21RDS in wrong subnetConnection timeoutPrivate subnet + SG
22No log retention policyCost + compliance gapTiered retention
23Testing only via APISMTP path untestedTest both paths
24No complaint workflowReputation decayWeekly review cadence
25Choosing Postal for 10M+/day without MTA planCost/complexityEvaluate KumoMTA or SES

Production Readiness Checklist (100+)

Infrastructure

  • Dedicated EC2 provisioned
  • Elastic IP attached
  • Ubuntu 22.04 LTS hardened
  • Docker and Compose installed
  • EBS gp3 sized for Docker growth
  • VPC flow logs enabled
  • NAT not required for public-subnet EC2 SMTP path documented

Database

  • RDS MariaDB 10.6+ provisioned
  • Multi-AZ enabled (production)
  • Parameter group: innodb_log_file_size sized
  • Automated backups configured
  • SG allows 3306 from EC2 only

Postal platform

  • install helper cloned to /opt/postal/install
  • postal bootstrap completed
  • postal.yml v2 format with RDS hosts
  • postal initialize successful
  • Admin user created
  • postal start — all containers up
  • Caddy TLS valid
  • First virtual mail server created

DNS

  • postal.example.com A record
  • PTR aligned
  • spf.postal.example.com TXT
  • rp.postal.example.com records + DKIM
  • routes.postal.example.com MX
  • track.postal.example.com (if used)
  • Customer domain SPF includes

Security

  • Secrets Manager for DB passwords
  • SSM Session Manager enabled
  • SSH restricted
  • signing.key permissions verified
  • IAM least privilege

Deliverability

  • IP warmup plan documented
  • IP pools configured
  • DMARC on sending domains
  • Postmaster tools access
  • Complaint threshold defined

Monitoring

  • CloudWatch agent deployed
  • CPU/disk/RDS alarms
  • Synthetic API send probe
  • Log archive to S3

Operations

  • Backup of /opt/postal/config to S3
  • Upgrade runbook documented
  • DR drill scheduled
  • On-call escalation defined

Networking (extended)

  • Internet Gateway attached to VPC
  • Public route table has 0.0.0.0/0 to IGW
  • Private route tables for RDS subnets only
  • NACLs documented (keep simple; SGs primary)
  • Elastic IP association documented in runbook
  • SMTP submission sources restricted in SG
  • Outbound HTTPS allowed for Docker pulls and apt
  • DNS resolver reachable from EC2
  • NTP/chrony synchronized
  • IPv6 posture documented (Postal docs show AAAA optional)

SMTP and API (extended)

  • SMTP port 25 listener verified (postal status)
  • SMTP submission on 587 tested if enabled
  • HTTP API send test from application subnet
  • Webhook endpoints reachable from worker
  • Bounce webhook URL registered per mail server
  • Message retention policy documented
  • Maximum message size aligned with RDS innodb setting
  • Rate limits per mail server reviewed
  • Suppression lists exported periodically
  • Dead-letter / held message review process defined

Compliance and audit (extended)

  • CloudTrail enabled for AWS API changes
  • Config rules for public SG detection
  • GuardDuty enabled
  • Inspector scans on EC2
  • Log retention meets policy
  • Access reviews scheduled quarterly
  • Change management ticket for postal.yml edits
  • Data classification for message metadata documented
  • Encryption at rest on RDS and EBS verified
  • Incident response playbook includes email outage

Scaling and HA (extended)

  • Capacity model by mail server documented
  • RDS storage autoscaling threshold set
  • EC2 vertical scale trigger defined (CPU sustained > 70%)
  • Second EC2 standby image documented for active-passive
  • Config sync mechanism (S3 versioned bucket)
  • Failover DNS/EIP runbook tested
  • Performance baseline captured before scale-up
  • Docker image version pinned in upgrade notes
  • Worker restart procedure documented
  • Load test on API path completed in staging

Webhooks, Bounces, and Event Handling

Postal exposes delivery events through webhooks configured per mail server. On AWS, terminate webhooks on HTTPS endpoints in your VPC (ALB + ACM is appropriate here — unlike SMTP).

Event typeOperational use
MessageSentConfirm injection accepted
MessageDelayedQueue/backpressure signal
MessageDeliveryFailedHard/soft failure triage
MessageBouncedSuppression list updates
MessageHeldPolicy/quarantine review

Point webhooks at an internal API Gateway or ALB-backed service with authentication. Log webhook payloads to S3 via Firehose for replay during incident analysis.

Upgrading Postal v3

Postal upgrades require reading official upgrading documentation before each change. Production-safe pattern on AWS:

  1. Snapshot RDS and /opt/postal/config to S3.
  2. Pull latest install helper: cd /opt/postal/install && sudo git pull.
  3. Run postal upgrade (per current install CLI).
  4. Verify postal status — all containers healthy.
  5. Send synthetic API + SMTP probes before restoring full traffic.

Never upgrade during peak send windows. Treat signing.key immutability as a hard constraint unless you understand DKIM and signing impacts.

CloudWatch Alarm Example

# Context: CloudFormation snippet — EC2 CPU high for Postal host
Resources:
  PostalCpuAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: postal-ec2-cpu-high
      Namespace: AWS/EC2
      MetricName: CPUUtilization
      Dimensions:
        - Name: InstanceId
          Value: i-0123456789abcdef0
      Statistic: Average
      Period: 300
      EvaluationPeriods: 3
      Threshold: 80
      ComparisonOperator: GreaterThanThreshold
      AlarmActions:
        - arn:aws:sns:us-east-1:123456789012:email-ops-alerts

Add companion RDS DatabaseConnections and FreeStorageSpace alarms — worker backlog often correlates with DB pressure before CPU spikes on EC2.

IAM Instance Role (Minimal)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue",
        "cloudwatch:PutMetricData",
        "logs:CreateLogStream",
        "logs:PutLogEvents",
        "s3:PutObject"
      ],
      "Resource": "*"
    }
  ]
}

Tighten Resource ARNs to your Secrets Manager secret, log groups, and backup bucket in production accounts.

Postal v3 vs KumoMTA on AWS — Decision Detail

Both run on EC2 with Elastic IPs and Route53 DNS. The operational split:

DimensionPostal v3KumoMTA
Primary interfaceWeb UI + HTTP APIPolicy files (Lua) + SMTP
Install modelDocker Compose via postal CLINative packages + systemd
DatabaseMariaDB requiredNot required for core MTA
Best volume band50k-1M emails/day typical1M+ with shaping investment
Multi-tenantVirtual mail servers (built-in)Tenant metadata in policy
Learning curveLower for app developersHigher; rewards dedicated mail ops

Our take: start with Postal when the team needs a visible control plane and HTTP API sends. Graduate to KumoMTA when queue policy complexity and throughput outgrow UI-driven configuration — or move outbound to SES when ops cost exceeds ESP economics.

DNS Flow Diagram

flowchart TD
  cfg[postal.yml dns block] --> r53[Route53]
  r53 --> mainHost[postal.example.com A]
  r53 --> spfInc[spf.postal.example.com TXT]
  r53 --> rpDom[rp.postal.example.com MX TXT DKIM]
  r53 --> routesDom[routes.postal.example.com MX]
  mainHost --> ptr[PTR on Elastic IP]
  ptr --> trust[MailboxProviderTrust]

Disaster Recovery Flow

flowchart LR
  outage[EC2Failure] --> promote[AttachEIPtoStandby]
  promote --> restore[RestoreConfigFromS3]
  restore --> rds[RDSMultiAZFailoverIfNeeded]
  rds --> verify[APIandSMTPProbes]
  verify --> traffic[RestoreSends]

What to Do This Week

  1. Provision EC2 + RDS MariaDB + Elastic IP in a non-production AWS account.
  2. Run postal bootstrap and point postal.yml at RDS.
  3. Publish Postal DNS records (spf, return-path, main host) in Route53.
  4. Create first virtual mail server; send API and SMTP test messages.
  5. Enable CloudWatch alarms and config backup to S3.
  6. Document IP warmup plan before customer traffic.

What This Post Doesn’t Cover

  • Hosted Postal offerings (third-party managed Postal platforms).
  • Full Kubernetes deployment of Postal (EC2 Docker host is the official path).
  • Postal v2 migration specifics — see Postal upgrading docs.
  • Legal/compliance counsel per jurisdiction.

For hybrid or migration paths, see Amazon SES consulting and How to Migrate from SendGrid to Amazon SES.

FactualMinds Expert Recommendations

FactualMinds is an AWS Select Tier Consulting Partner with email infrastructure expertise.

ScenarioRecommendation
Self-hosted UI + API platformPostal v3 on EC2 + RDS
High-volume policy MTAKumoMTA on AWS
Managed deliverabilityAmazon SES
ESP migrationSES migration service

Contact us to discuss your email infrastructure →

PP
Palaniappan P

AWS Cloud Architect & AI Expert

AWS-certified cloud architect and AI expert with deep expertise in cloud migrations, cost optimization, and generative AI on AWS.

AWS ArchitectureCloud MigrationGenAI on AWSCost OptimizationDevOps

Recommended Reading

Explore All Articles »