Skip to content
Back to Blog
infrastructure
engineering
postgres

Running an Email API on Managed Postgres: How PostStack Uses HostStack

By Michael Andersen7 min readFact-checked May 27, 2026

I used to run Postgres myself. Bare-metal Hetzner box, hand-tuned postgresql.conf, pgBackRest cron, the works. I did it because I'd done it before at scale and because the cost difference between a self-managed VPS and a managed Postgres was painful to look at on a small product. Then I added it up honestly: the time I spent on point-in-time recovery drills, version upgrades, TLS cert plumbing on the database listener, replica failover testing. PostStack is run by one person. That time is not free.

The whole platform — every email row, every contact, every webhook delivery record, every audit log — now sits on a HostStack-managed Postgres cluster. This post is what that move actually looked like: what we moved, what we didn't, the surprises, and what the math works out to when the operator is exactly one human.

What's actually in PostStack's database

Some shape numbers, so the rest of the post is grounded:

  • Primary OLTP tables: emails, email_events, contacts, domains, broadcasts, webhook_deliveries, users, teams, plus the OAuth / 2FA / API key auxiliaries.
  • Hottest table by write rate: email_events — every open, click, bounce, complaint, and delivery report lands here. Partitioned by month. ~99% of write volume.
  • Largest by row count: emails, retained for the per-plan log window (7-90 days). Older rows tombstone to S3 if the plan requested it.
  • Workload shape: bursty inserts on send, near-constant inserts on tracking, periodic large reads from the dashboard's analytics queries. Index selectivity matters more than raw IOPS at our current scale.

None of that is exotic. It is the shape of any transactional SaaS database with an event-heavy table riding alongside it. The reason it matters for the managed-vs- self decision is that there's nothing on this list a managed service can't host. Some workloads (heavy extensions, custom kernels, big in-memory analytical clusters) genuinely can't sit on a managed Postgres. PostStack's isn't one of them.

What we kept self-managed

Three things stayed on the bare-metal stack and probably always will:

  1. Postfix and Dovecot. The MTA and the IMAP server. These have strong locality requirements — the queue spool needs to be on local disk, port 25 needs to bind directly, and the entire reputation story is tied to the box's IP. No managed offering would help here, and frankly nobody in the EU offers managed Postfix in a way that doesn't immediately become "we run it for you on the same VPS you'd have rented."
  2. BullMQ workers. They're Bun processes that read from Redis and drain the email queue. They have to live next to Postfix because they hand off over the unix socket. Bog-standard Hetzner boxes, no orchestration magic.
  3. Rspamd. Outbound filtering and DKIM signing for high-volume tenants. Same reason — it lives in the SMTP path.

What moved off was Postgres, Redis, and the DNS that fronts all of it. Three services that are textbook commodity-managed-service candidates, and three services where the operator hours add up faster than people admit.

What the move actually looked like

Twenty minutes of clock time, mostly waiting for pg_dump and pg_restore to run.

  1. Provisioned a HostStack Postgres 18 cluster, same version we were running, in Helsinki. One primary + one read replica. Encrypted at rest, TLS-only, private network only — the API container is the only thing that can reach it.
  2. pg_dump -Fc on the source, copied the file via private network to the cluster's bouncer endpoint, pg_restore -j 4 on the other side. Lit up a third Postgres alongside the running one rather than cutting over blind.
  3. Switched DATABASE_URL in the API container's env, restarted, ran the smoke suite. Total user-facing downtime: about 90 seconds because the API container restart blocks new requests while the connection pool re-establishes.
  4. Watched event-rate dashboards for an hour. Decommissioned the old box the following week after backups were proven to restore cleanly from the new cluster.

The reason it was twenty minutes and not a day is that the schema is on Drizzle migrations, the connection string is one env var, and nothing in the application layer cares whether the database is on the same host. Apps that depend on filesystem access, weird Unix-socket connections, or local-only extensions would have a harder time. PostStack's API is built to be portable specifically so this kind of move stays cheap.

What stopped being our problem

  • Backups. Continuous WAL streaming to HostStack's backup storage, point-in-time-recoverable to any second in the last 30 days. Restore tested monthly by HostStack, not by me. I get a notification when a drill passes.
  • Failover. The cluster runs primary + replica with automatic promotion. The bouncer endpoint is the same string before and after; the application reconnects in seconds, with the pool dropping the broken primary gracefully. I have not had to invoke this manually in production, but I have tested it deliberately in staging — total observed downtime: 14 seconds.
  • Minor version upgrades. A maintenance window I get to schedule. Major version upgrades are the same with a heads-up notice. Last time I did a major version upgrade by hand, on the previous box, it took an evening and a side helping of stress; now it's a checkbox.
  • Slow query introspection. pg_stat_statements exposed in the dashboard with no setup, plus query plans on demand. PostStack's API also writes its own slow-query log to the same database, so I get both ends of the picture in one place.

What we pay for this

At PostStack's current scale, the HostStack-managed cluster costs about 4× what the bare-metal box did. That sounds bad until I multiply my old time on database operations — including the post-incident debugging from one particularly memorable replica failover that didn't — by a fair hourly number. The breakeven is somewhere around two hours per month.

It is worth more than that for a one-person product. It would probably be worth less for a team of five engineers where one of them already owns Postgres operations as a real job. The decision is not about cost; it's about which 0.5 FTE you'd rather keep busy.

The Redis and DNS pieces

Redis runs PostStack's BullMQ queues and the rate-limiter. It used to be a tiny VM with a single instance and no persistence. It moved to a HostStack-managed Redis cluster on the same day as the Postgres move, with the same shape — primary + replica, automatic failover, monitored backups. The job queue is durable enough that a Redis failover doesn't lose work.

DNS is on HostStack too. PostStack's own zones (poststack.dev, api.poststack.dev, the MTA-STS policy host) all sit on HostStack PowerDNS clusters with DNSSEC. The same multi-signer setup HostStack offers external customers — and yes, it's eaten its own dog food on the production zone since long before the integration shipped. That story is what eventually became the HostStack DNS integration for PostStack tenants.

Should you do the same?

The honest version: it depends on whether your team's bottleneck is engineering time or budget. If you have engineers cheaper than the managed bill, and Postgres is in their working knowledge, self-host. If your bottleneck is one or two people building everything, the managed bill is a bargain for the operating overhead it removes.

PostStack is in the second camp. So is most of the small-team SaaS world I talk to. The difference between "the database is fine" and "the database is fine because someone competent is watching it" is the kind of cost that doesn't appear on an infra invoice until the morning it does.

Frequently asked questions

Why move from self-hosted Postgres to managed Postgres for an email API?

For a one- or two-person team, the operator hours saved on backups, failover testing, version upgrades, and TLS rotation are worth more than the ~4× managed-vs-bare-metal cost delta. For a larger team where someone already owns Postgres operations as a real job, the math is different and self-hosting can still be cheaper end-to-end.

What did PostStack keep self-managed after the database move?

Postfix, Dovecot, Rspamd, and the BullMQ workers. They have strong locality requirements — local-disk queue spool, port 25 binding, IP-bound deliverability reputation, unix-socket handoff — that managed offerings either cannot or do not address.

What was the downtime during the database migration?

About 90 seconds — the time for the API container to restart with a new DATABASE_URL and the connection pool to re-establish. The migration itself was pg_dump -Fc on the source and pg_restore -j 4 against the new cluster over private network, with the new database brought up alongside the old one rather than cut over blind.

Does the database stay in the EU after moving to a managed provider?

Yes if you choose carefully. PostStack uses HostStack Postgres in Helsinki, with WAL streaming and backup storage that remain EU-jurisdictional. "EU region of a US provider" satisfies the technical residency requirement but leaves the parent-jurisdiction exposure intact — for the strongest posture, both the data centre and the operator need to be EU.

Michael Andersen

Michael Andersen

Founder, PostStack

Building PostStack — a European email API hosted in Helsinki, Finland. Background in self-hosting Postfix, deliverability operations, and high-throughput backend systems.

Continue reading

Ready to get started?

Send your first email in under 5 minutes. Free plan included.