Run the Mailpipe backend on your own infrastructure
Mailpipe is a Next.js app backed by Supabase (Postgres + Auth + Storage) with Resend handling mail transport. This guide walks you through standing up the full stack yourself — your database, your domain, your keys.
Next 16 requires Node 18.18 or newer; the repo is built and tested against Node 20.
Self-hosted Supabase or supabase.com — Auth, Storage, and a Postgres database.
With a domain you control, verified for both sending and inbound receiving.
Ability to add MX, SPF, DKIM and a verification record.
Grab the source and move into the project directory. The repo is an npm workspace — the dashboard, the SDK, and the MCP server all live under one root.
git clone https://github.com/mailpipe/mailpipe.git cd mailpipe
You'll install dependencies in step 4, after the database and environment are configured.
Create a Supabase project (hosted or self-hosted). Mailpipe's schema lives as ordered SQL files in supabase/migrations/. They create the core tables — organizations, domains, mailboxes, messages, API keys, usage tracking — and the Row Level Security policies that isolate every tenant.
The simplest path is the Supabase CLI. Link your project, then push the migrations:
# Authenticate and link the CLI to your project supabase login supabase link --project-ref your-project-ref # Apply every migration in supabase/migrations/ in order supabase db push
You can apply the same migrations against any Postgres instance with psql. They are timestamp-prefixed, so applying them in filename order reproduces the schema exactly.
for f in supabase/migrations/*.sql; do psql "$DATABASE_URL" -f "$f" done
The schema depends on Supabase Auth
Several tables referenceauth.users and RLS policies use auth.uid(). Run these migrations against a database that has the Supabase Auth schema present — a stock Postgres without it will reject the foreign keys.Copy the example file and fill in your own values. Everything Mailpipe reads at runtime is documented in .env.example.
cp .env.example .env.local
NEXT_PUBLIC_SUPABASE_URLYour project URL, e.g. https://your-project-id.supabase.co. Public.NEXT_PUBLIC_SUPABASE_ANON_KEYThe anon/public key used by the browser client. Public.SUPABASE_SERVICE_ROLE_KEYBypasses Row Level Security. Server-side only — never expose to the browser.RESEND_API_KEYUsed to send outbound mail and to read received messages. Starts with re_.ENCRYPTION_KEYAES-256-GCM key (32 raw bytes, base64) that encrypts provider API keys at rest.FORWARDING_HMAC_SECRETSigns reply+TOKEN local-parts for per-member forwarding. At least 16 bytes of entropy.NEXT_PUBLIC_APP_URLBase URL for auth redirects. Defaults to http://localhost:3000 in development.FORWARD_EMAILBootstrap mode — forwards every inbound message to this address while you set things up.MAILPIPE_FORWARDER_DOMAINOverride the forwarder host. Defaults to mailpipe.dev — set this to your own relay domain.ENCRYPTION_KEY must decode to exactly 32 bytes — the app validates this at startup and throws otherwise. Generate it and the forwarding secret the same way:
# 32-byte base64 secret — use for ENCRYPTION_KEY and FORWARDING_HMAC_SECRET
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"Install dependencies across the workspace, then start the dev server.
npm install npm run dev
The dev server starts on http://localhost:3000 by default. To pin a different port, pass it through:
npm run dev -- -p 3003
Type-check at any time with npm run typecheck, and run the unit suite with npm test.
Add your domain in the Resend dashboard and create the records it gives you. You need an MX record so inbound mail reaches Resend, plus SPF and DKIM so your outbound mail authenticates:
# Inbound — route incoming mail to Resend Type: MX Host: @ Priority: 10 Value: <resend-inbound-host> # SPF — authorize Resend to send for your domain Type: TXT Host: @ Value: v=spf1 include:resend.net ~all # DKIM — Resend gives you the exact record when you add the domain Type: TXT Host: resend._domainkey Value: <dkim-key-from-resend>
Use the exact host and key values Resend shows for your domain — the snippet above is the shape, not a copy-paste secret. DNS can take up to 48 hours to propagate.
In the Resend dashboard, configure the inbound webhook to POST received messages to your deployment. Mailpipe ingests them at an org-scoped route:
https://your-deployment.example.com/api/v1/inbound/<orgId>
<orgId> is the UUID of the organization row the mail should land in. There is also a more specific /api/v1/inbound/<orgId>/<domainId> variant if you want to scope a webhook to a single domain. The handler parses the payload, finds or creates the target mailbox, stores the message, and (if configured) forwards a copy.
Forwarder domain
The reply/forwarding flow defaults its host tomailpipe.dev. When self-hosting on your own domain, set MAILPIPE_FORWARDER_DOMAIN to the domain whose MX you pointed at Resend so reply tokens are minted on a host you control.For production, build the optimized bundle and run the Next.js server:
npm run build npm run start
Provide the same environment variables from step 3 to your host. Mailpipe runs anywhere that can host a Next.js app — Vercel, a container, or a Node process behind your own reverse proxy. Set NEXT_PUBLIC_APP_URL to your public URL so auth redirects resolve correctly.
You own the database and the keys
When you self-host, your Supabase project holds all mail and tenant data. KeepSUPABASE_SERVICE_ROLE_KEY and RESEND_API_KEY server-side only — never ship them to the browser or commit them to version control. The service-role key bypasses Row Level Security, so anything that holds it can read and write every tenant. Only the two NEXT_PUBLIC_ values are safe to expose to the client.Our team is here to help with anything from setup to scaling.