Sit in front of Resend or SES, capture every inbound and outbound email, archive it permanently, and give it a searchable inbox.

Every transactional email provider expires logs after 30–90 days. For a small SaaS that means you cannot answer “did this user receive their invite email six months ago?” — the record is gone. For compliance, audits, or debugging a subtle delivery bug, that gap is genuinely dangerous.
The fix sounds simple: just save a copy before you send. The reality is that outbound needs to be proxied or hooked, inbound needs a webhook receiver and an MX setup, and the stored emails need to be queryable, not just archived. EmailOS does all three without replacing your existing provider — it augments it.
Two capture paths — an outbound proxy SDK and an inbound webhook — feed into a single Postgres store. The UI is a read layer over that store. The provider is swappable at any point; the archive is not touched.
Capturing via provider webhooks means you depend on the provider delivering those webhooks — and you lose the archive if you switch providers. A thin wrapper around the send call captures before the provider is ever involved, so the archive is truly provider-independent.
Storing full email blobs in Postgres is an antipattern that makes the database huge and slow. EmailOS stores headers, subject, body text, and thread relationships in Postgres — where they are queryable — and offloads attachment binaries to R2 with a foreign key. Search stays fast; storage stays cheap.
Email search is overwhelmingly subject-line and sender lookups, not full-document relevance ranking. Postgres tsvector handles that comfortably and keeps the stack to one datastore. Meilisearch or Typesense would be premature for the corpus size EmailOS targets.
Real-world emails arrive as multi-part MIME trees — HTML body, plain-text fallback, inline images with Content-ID references, attachments with mismatched charset declarations. Parsing them into a clean stored representation without losing any part required careful handling of every edge case the standard allows and several it does not.
Email threading relies on In-Reply-To and References headers — which not every sender sets correctly. Building a thread view that degrades gracefully to subject-line matching when headers are absent, without creating false threads, was more logic than expected.
Webhook providers retry on timeout. If the receiver is slow, the same email arrives twice. The ingest path deduplicates on Message-ID with an upsert, so replays are safe — but getting that upsert right under concurrent delivery took care with Drizzle's conflict clauses.
“Your email provider owns your logs for 30 days. After that, you never sent those emails. EmailOS fixes that in one SDK line.”— Parm, on EmailOS