Skip to content
Yasir Uslu
All work

Email on my own domain

Mail — @usluso.com

A mail service on the usluso.com domain. Invited people pick their own address; incoming and outgoing messages are grouped into threads, drafts save themselves and new mail arrives as a push notification. It has a three-pane desktop layout and a one-handed phone layout, and opens as its own app at mail.usluso.com.

Status
Live · invite only
Tools
Next.js 16 · TypeScript · Cloudflare Workers · Supabase · PostgreSQL RLS · Resend · Web Push · Playwright
Mail — @usluso.com screenshot

The problem

The site runs on Vercel, which doesn’t accept mail (SMTP). It had to receive mail, keep everyone’s mailbox separate, avoid landing in spam and render untrusted HTML safely, all without running a mail server.

My role

Sole developer: the receiving Worker, database schema and security rules, sending, notifications, UI and tests.

Approach

  1. 01

    Receiving: a Cloudflare Email Worker

    Cloudflare Email Routing hands incoming mail to a Worker. It parses the message with postal-mime, finds the recipient’s mailbox, uploads attachments and writes the record. Unknown, disabled or full mailboxes bounce with a reason; if storing fails the sending server retries, so nothing is lost.

  2. 02

    Everyone sees only their own mail

    Postgres row-level security ties every message to its mailbox owner; nobody, the admin included, can read someone else’s mail. Claiming an address happens in a database function that checks the invite, format, one address per person and spoofable names (support, info…).

  3. 03

    Threads in the database

    A trigger links every message to its thread on insert: In-Reply-To and References first, then the same subject with the same correspondent within 60 days. The list, search and bulk moves are single SQL functions.

  4. 04

    Untrusted HTML, safely

    HTML mail renders in a sandboxed frame that can’t run scripts; scripts, redirects and embeds are stripped as well. Remote images stay blocked until the reader allows them, so senders can’t track opens. Plain messages without their own colors follow the app theme.

  5. 05

    Instant and installable

    New mail appears instantly through Supabase Realtime; once the Worker stores it a Web Push (VAPID) notification goes out and opens the thread. Sending goes through Resend with SPF, DKIM and DMARC in place. The Worker redeploys itself from GitHub Actions after tests whenever its code changes.

The hard part

Matching replies to my own mail

The sending service generates its own Message-ID, so replies to mail I sent couldn’t be linked by headers. A second path matches the subject (with Re:/Fwd: stripped) and the correspondent, only within the same mailbox and the last 60 days, so messages from different people with the same subject never merge.

Quality

API routes and mail parsing are covered by Vitest, and the UI (swipe to delete, multi-select, undo send, claiming an address) by Playwright on phone and desktop viewports.