Skip to content

sayedhfatimi/vemail

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

vemail

vemail

Your inbox, on your own domain.

A self-hosted, open-source email client that sends and receives on your own custom domain via Resend — no Gmail, no Proton, no big-name provider. Ships as one Docker image on a self-hosted Convex backend.


vemail turns a domain you already own + a Resend account into a real, private inbox — the send/receive experience of Gmail or Proton, but you host it and you own the data. Point your domain's mail at Resend, connect it in the dashboard, and every address you claim lands in a clean, fast inbox you control.

Features

  • Real send + receive on your own domain — no forwarding hacks, no "via gmail.com".
  • Live, reactive inbox — powered by Convex subscriptions, new mail appears instantly with no refresh and no polling, and read/starred state syncs across your devices in real time.
  • Multi-user, private inboxes — invite others; each person owns their own prefix@domain addresses and sees only their own mail. Invite-only (no open registration).
  • Privacy-first — remote images and tracking pixels blocked by default, sender SPF/DKIM/DMARC trust badges, and inbound HTML sanitized on arrival.
  • A proper client — rich-text composer, drafts (autosaved), attachments, threaded conversations, reply / reply-all / forward, undo-send, labels, star, archive, trash, and a ⌘K command palette that filters the folder you're in.
  • Guided setup — vemail shows you the exact DNS records to add and polls until your domain verifies.
  • Encrypted credentials — your Resend key + webhook secret are AES-256-GCM encrypted at rest; no API keys in your environment files.
  • 2FA, light/dark themes, and a PWA.

⚠️ vemail must be publicly accessible

vemail is not an air-gapped app. Resend delivers your inbound mail by calling a webhook, so your instance must be reachable from the public internet over HTTPS. If it can't be reached from the outside, you can send but you will not receive mail. Plan to run it behind a reverse proxy with a real domain and TLS.

Requirements

  • Docker + Docker Compose, and openssl.
  • A domain you control (any registrar) + a free Resend account.
  • A public HTTPS entry point (reverse proxy). Two Convex origins must be publicly reachable — see Deploy.

Deploy

git clone https://github.com/sayedhfatimi/vemail && cd vemail
sh scripts/setup.sh          # generates the 4 secrets into .env (mode 600)
# edit .env: set CONVEX_PUBLIC_URL, SITE_URL, CONVEX_SITE_PUBLIC_URL for your host
docker compose up -d

scripts/setup.sh generates BETTER_AUTH_SECRET, a pinned CONVEX_INSTANCE_SECRET, the matching CONVEX_SELF_HOSTED_ADMIN_KEY, and the VEMAIL_ENCRYPTION_KEY that encrypts your Resend credentials. Pinning the instance secret keeps the admin key valid across redeploys — no deploy→generate-key→redeploy dance. Deploy onto a fresh vemail-convex-data volume so the pinned secret matches.

Expose both Convex ports

Self-hosted Convex serves two origins and both must be publicly reachable behind your proxy:

Port Purpose Env var
3210 API / cloud — the dashboard/browser talks to this CONVEX_PUBLIC_URL
3211 HTTP actions / site — Resend delivers inbound webhooks here CONVEX_SITE_PUBLIC_URL
3000 the vemail web app SITE_URL

The Resend inbound webhook URL to configure is <CONVEX_SITE_PUBLIC_URL>/resend-webhook — and once you set CONVEX_SITE_PUBLIC_URL, vemail shows you the exact URL (with a copy button) in Settings → Administration.

Easiest public setup: a Cloudflare Tunnel

If your domain's DNS is on Cloudflare, a Cloudflare Tunnel is the simplest way to expose vemail's three origins publicly over HTTPS — no open ports, no manual reverse proxy, no certificates to manage.

  1. Install cloudflared (sudo pacman -S cloudflared, brew install cloudflared, or sudo apt install cloudflared).

  2. In the Cloudflare dashboard → Zero Trust → Networks → Tunnels → Create a tunnel (cloudflared), name it (e.g. vemail), and run the connector command it gives you on the host. (That token is a secret — don't commit it.)

  3. Add three public hostnames to the tunnel, mapping subdomains on your domain to vemail's local ports (Cloudflare creates the DNS records for you):

    Public hostname Service
    mail.yourdomain.com http://localhost:3000
    convex.yourdomain.com http://localhost:3210
    convex-http.yourdomain.com http://localhost:3211
  4. Set your .env to match, then docker compose up -d:

    SITE_URL=https://mail.yourdomain.com
    CONVEX_PUBLIC_URL=https://convex.yourdomain.com
    CONVEX_SITE_PUBLIC_URL=https://convex-http.yourdomain.com

    Your Resend webhook URL is then https://convex-http.yourdomain.com/resend-webhook.

Prefer a config file? The same three ingress rules work in ~/.cloudflared/config.yml:

tunnel: <TUNNEL_ID>
credentials-file: /root/.cloudflared/<TUNNEL_ID>.json
ingress:
  - hostname: mail.yourdomain.com
    service: http://localhost:3000
  - hostname: convex.yourdomain.com
    service: http://localhost:3210
  - hostname: convex-http.yourdomain.com
    service: http://localhost:3211
  - service: http_status:404

For a step-by-step walkthrough of Cloudflare Tunnels, see this guide: Cloudflare Tunnels for local dev.

Environment

Var Required Purpose
BETTER_AUTH_SECRET session signing (generated)
CONVEX_INSTANCE_SECRET pins the backend so the admin key is stable (generated)
CONVEX_SELF_HOSTED_ADMIN_KEY lets the app deploy Convex functions (generated)
VEMAIL_ENCRYPTION_KEY AES-256-GCM key that encrypts Resend creds (generated)
CONVEX_PUBLIC_URL browser-reachable backend API (port 3210)
CONVEX_SITE_PUBLIC_URL public HTTP-actions origin (port 3211) for webhooks
SITE_URL public URL of the vemail app
POSTGRES_URL external Postgres instead of the bundled SQLite volume
ANALYTICS_SCRIPT_URL / ANALYTICS_SITE_ID optional privacy-friendly analytics

Your Resend API key, webhook secret, and domains are NOT set here — the admin enters them in the dashboard (Settings → Administration) and they're stored encrypted.

First run

  1. Open the app and create the admin account (shown only on a fresh instance).
  2. Settings → Administration → connect Resend (paste API key + webhook secret).
  3. Add your domain; add the DNS records vemail shows; wait for verification.
  4. Claim an address (Settings → Your addresses), then invite others.

The full Resend + DNS + webhook walkthrough is in docs/USER-GUIDE.md.

Updating

docker compose pull && docker compose up -d

Local development

docker compose up -d convex          # boot only the backend
bunx convex dev --once               # push functions + generate types
bun install && bun run dev           # app on http://localhost:3000

Codegen needs a running backend — there is no always-on dev deployment. convex/_generated is committed and never hand-edited (see CLAUDE.md).

Roadmap

vemail's M1 is a complete daily-driver. Contributions toward what's next are very welcome:

  • Filters / rules engine (auto-label, archive, route inbound)
  • Snooze, scheduled send, vacation auto-responder
  • Contacts / address book with autocomplete + auto-collect
  • Spam / junk handling + per-recipient bounce suppression
  • .eml / mbox import & export
  • Message templates / snippets
  • Catch-all addresses (receive all *@domain)
  • Multi-admin management (promote/demote)

Contributing

Issues and PRs welcome. Please keep the four quality gates green — biome check, tsc --noEmit, bun run test, and bun run build — and read CLAUDE.md for the project conventions (bun-only, self-hosted-Convex codegen, commit/tag rules).

Backup

Everything (database + file storage) lives in the vemail-convex-data Docker volume — back it up as a unit.

License

MIT.

About

Self-hosted, open-source custom-domain email client powered by Resend — your inbox, on your own domain.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages