Self-Hosting Walkthrough

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.

Prerequisites

Node.js 20+

Next 16 requires Node 18.18 or newer; the repo is built and tested against Node 20.

A Postgres / Supabase instance

Self-hosted Supabase or supabase.com — Auth, Storage, and a Postgres database.

A Resend account

With a domain you control, verified for both sending and inbound receiving.

DNS control for your domain

Ability to add MX, SPF, DKIM and a verification record.

1

Clone the repository

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.

bash
git clone https://github.com/mailpipe/mailpipe.git
cd mailpipe

You'll install dependencies in step 4, after the database and environment are configured.

2

Provision the database

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:

bash
# 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

No CLI? Run the SQL directly

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.

bash
for f in supabase/migrations/*.sql; do
  psql "$DATABASE_URL" -f "$f"
done

The schema depends on Supabase Auth

Several tables reference auth.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.
3

Configure your environment

Copy the example file and fill in your own values. Everything Mailpipe reads at runtime is documented in .env.example.

bash
cp .env.example .env.local

Required keys

  • 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.

Optional keys

  • 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.

Generate the secrets

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:

bash
# 32-byte base64 secret — use for ENCRYPTION_KEY and FORWARDING_HMAC_SECRET
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
4

Install and run

Install dependencies across the workspace, then start the dev server.

bash
npm install
npm run dev

The dev server starts on http://localhost:3000 by default. To pin a different port, pass it through:

bash
npm run dev -- -p 3003

Type-check at any time with npm run typecheck, and run the unit suite with npm test.

5

Point your domain and wire the inbound webhook

Configure DNS in Resend

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:

DNS records
# 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.

Send inbound email to your deployment

In the Resend dashboard, configure the inbound webhook to POST received messages to your deployment. Mailpipe ingests them at an org-scoped route:

Webhook URL
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 to mailpipe.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.
6

Build and deploy

For production, build the optimized bundle and run the Next.js server:

bash
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. Keep SUPABASE_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.

Related guides

Still have questions?

Our team is here to help with anything from setup to scaling.

Contact support