<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dispetch</title>
    <description>The latest articles on DEV Community by Dispetch (@__227a2843e88635).</description>
    <link>https://dev.to/__227a2843e88635</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4012450%2F5ee2b511-319e-4e3c-9883-3ef1cc8b2b5f.png</url>
      <title>DEV Community: Dispetch</title>
      <link>https://dev.to/__227a2843e88635</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/__227a2843e88635"/>
    <language>en</language>
    <item>
      <title>How I built a disposable email service in TypeScript (SMTP, SSE, and a single VPS)</title>
      <dc:creator>Dispetch</dc:creator>
      <pubDate>Thu, 02 Jul 2026 14:52:38 +0000</pubDate>
      <link>https://dev.to/__227a2843e88635/how-i-built-a-disposable-email-service-in-typescript-smtp-sse-and-a-single-vps-1pp9</link>
      <guid>https://dev.to/__227a2843e88635/how-i-built-a-disposable-email-service-in-typescript-smtp-sse-and-a-single-vps-1pp9</guid>
      <description>&lt;h1&gt;
  
  
  How I built a disposable email service in TypeScript
&lt;/h1&gt;

&lt;p&gt;Temp-mail services are everywhere, but most of them are slow, ad-stuffed, and&lt;br&gt;
feel like they haven't been touched since 2012. I wanted to know how hard it&lt;br&gt;
actually is to build a good one — so I built Mailfly. Here's the architecture,&lt;br&gt;
the parts that were harder than expected, and what I'd tell anyone attempting&lt;br&gt;
the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea
&lt;/h2&gt;

&lt;p&gt;A disposable email service has three jobs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hand someone a throwaway address instantly (no signup).&lt;/li&gt;
&lt;li&gt;Receive real email sent to that address and show it fast.&lt;/li&gt;
&lt;li&gt;Delete everything when the address expires.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. The hard part isn't the concept — it's receiving mail reliably&lt;br&gt;
without becoming a spam relay, and showing it to the user in near-real-time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Receiving mail: a custom SMTP server
&lt;/h2&gt;

&lt;p&gt;The internet delivers email over SMTP, so to receive mail you need to run an&lt;br&gt;
SMTP server that the world can reach on port 25. I used the smtp-server&lt;br&gt;
library and built a receive-only server — no authentication, and crucially&lt;br&gt;
not an open relay.&lt;/p&gt;

&lt;p&gt;Two rules keep it safe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reject any recipient whose domain isn't ours (otherwise you're an open relay
that spammers will abuse within hours).&lt;/li&gt;
&lt;li&gt;Reject any recipient that isn't a live inbox (return SMTP 550).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a valid message arrives, the server parses it, pulls out the OTP code and&lt;br&gt;
any links, caps the size, stores attachments as base64, and publishes an event&lt;br&gt;
to Redis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Showing mail instantly: SSE over polling
&lt;/h2&gt;

&lt;p&gt;Most temp-mail sites poll: the browser asks "any mail yet?" every few seconds.&lt;br&gt;
It's wasteful and feels laggy. I used Server-Sent Events instead.&lt;/p&gt;

&lt;p&gt;The flow: SMTP server receives mail -&amp;gt; publishes to a Redis channel -&amp;gt; the API&lt;br&gt;
is subscribed -&amp;gt; it pushes the message down an open SSE connection to the&lt;br&gt;
browser -&amp;gt; the inbox updates the instant the mail lands.&lt;/p&gt;

&lt;p&gt;The subtle bug I hit: my SSE effect depended on the whole inbox object, which&lt;br&gt;
got recreated on every timer tick, so the connection reconnected constantly&lt;br&gt;
and flooded the server logs. Fixing the dependency to just the inbox ID and&lt;br&gt;
token solved it. Lesson: with long-lived connections, be very deliberate about&lt;br&gt;
what triggers a reconnect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storage and expiry
&lt;/h2&gt;

&lt;p&gt;Postgres holds inboxes, messages, and attachments. The nice trick is&lt;br&gt;
ON DELETE CASCADE: when a background job deletes expired inboxes once a&lt;br&gt;
minute, all their messages and attachments vanish automatically. No orphans,&lt;br&gt;
no manual cleanup, and it matches the privacy promise — when the timer hits&lt;br&gt;
zero, the data is genuinely gone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack, end to end
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;SMTP receiver: TypeScript + smtp-server&lt;/li&gt;
&lt;li&gt;API: Fastify, with SSE endpoints and an OTP long-poll fallback&lt;/li&gt;
&lt;li&gt;Data: PostgreSQL + Redis (pub/sub for the live inbox)&lt;/li&gt;
&lt;li&gt;Front-end: Next.js, SSR, 5 languages&lt;/li&gt;
&lt;li&gt;Infra: Docker Compose, Caddy for automatic TLS, one VPS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping it all in one language meant I could share code (OTP extraction,&lt;br&gt;
local-part generation) between the SMTP server, the API, and the SDKs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hardest non-code problem: blocklists
&lt;/h2&gt;

&lt;p&gt;Writing the service was the easy part. Keeping disposable domains working is&lt;br&gt;
the ongoing battle. Sites block known temp-mail domains, and if your domain&lt;br&gt;
ends up on a spam blocklist, deliverability drops. My approach: keep the brand&lt;br&gt;
domain clean (never use it for public throwaway inboxes), rotate disposable&lt;br&gt;
domains, and keep inbox lifetimes short so the surface area stays small.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you build one?
&lt;/h2&gt;

&lt;p&gt;As a learning project — absolutely. You'll touch SMTP, real-time transport,&lt;br&gt;
queue/pub-sub, and infra/TLS in one shot. As a business, the moat is operational&lt;br&gt;
(deliverability, abuse handling), not the code. The code you can write in a&lt;br&gt;
couple of weeks; staying unblocked is forever.&lt;/p&gt;

&lt;p&gt;If you want to see the live inbox in action, it's at &lt;a href="https://mailfly.email" rel="noopener noreferrer"&gt;https://mailfly.email&lt;/a&gt;.&lt;br&gt;
The free tier is open and there's an API + JS/Python SDKs if you need throwaway&lt;br&gt;
inboxes in your tests.&lt;/p&gt;

&lt;p&gt;Happy to answer architecture questions in the comments.&lt;/p&gt;

&lt;h1&gt;
  
  
  typescript #webdev #node #showdev
&lt;/h1&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>showdev</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
