Redakt

A design-first, open-source mail client and provider. Host mail on a domain you own or connect Gmail, then read and send from a quiet, account-aware inbox.

Redakt is the product layer: onboarding, connected accounts, DNS, and the inbox. Stalwart is the hosted-mail engine (SMTP, JMAP, IMAP, DKIM); Gmail stays the source of truth for connected Google accounts. This repository is MIT and does not fork Stalwart. Stalwart stays AGPL when you run it.

The project is early (0.1.0). Demo mode is the fastest way to look at the product. Live mode talks to a real mail engine on your machine.

Interface inspiration: Define and Tatem.

Run it

You need pnpm 11 and Node 20+. Live mode also needs Docker.

Demo

No Postgres, no Stalwart, no port 25.

cp .env.example .env
pnpm install
pnpm dev

Open http://localhost:3000. Demo mode keeps a local session and loads fixture .eml files from fixtures/. Seed login is ada@redakt.local / demo.

Live (Postgres + Stalwart)

pnpm install
pnpm dev:live

dev:live creates .env with local defaults and independent authentication and stored-data secrets when it is missing. If .env already exists, it appends only documented settings that are not present and leaves every existing value untouched. It then starts Postgres and Stalwart, pushes the schema, and runs the app with DEMO_MODE=false.

ServicePort
Redakt3000
Postgres5432
Stalwart HTTP / JMAP8080
SMTP / SMTPS / IMAPS25 / 465 / 993

On first Stalwart boot, finish the wizard at http://localhost:8080/admin (admin / changeme unless you changed .env). Copy the generated admin into .env, close that tab, and use Redakt at http://localhost:3000.

The Stalwart Dashboard on HTTP will loop on a browser sign-in prompt. Cancel it. Redakt talks to Stalwart over JMAP, not that screen.

Step-by-step wizard values: deploy/stalwart/README.md.

docker compose up --build also runs the app in a container. Prefer pnpm dev:live while developing.

Gmail

Gmail connections use OAuth and the Gmail API in live mode. Add the Google client credentials to .env, then use Manage accounts → Add Gmail account. The complete OAuth, restricted-scope review, Pub/Sub, watch-renewal, encryption, and privacy runbook is in docs/gmail.md.

AI features

Focus is an optional, read-only view that groups recent inbox conversations by what needs a reply, has a deadline, is waiting on someone, is informational, or is probably low priority. It supports bring-your-own-key access to OpenAI, Anthropic, and Google Gemini.

Smart Compose uses the same bring-your-own-key provider connection. It can offer short inline completions while a message is being written and draft or revise a message in a selected tone. Inline completions are requested only after a typing pause. Every suggestion remains editable and is never sent automatically.

Each AI feature is off until the user selects a provider, adds a key, explicitly agrees to AI processing, and turns that feature on. Focus sends bounded content from at most 20 recent inbox conversations. Smart Compose sends the current draft and relevant message context. Redakt does not use this content to train models, and AI features never send mail or apply mailbox actions automatically. In live mode the provider key is encrypted at rest; in demo mode it stays only in process memory until the local server restarts.

Internet mail

Local live mode creates real mailboxes in Stalwart. It does not receive mail from Gmail or other public servers.

Inbound and outbound on the public internet need Stalwart on a host with usable port 25, a matching PTR record, and a warm IP — or an outbound relay. Set MAIL_HOSTNAME to that public MX host (not mail.example.com). The DNS wizard still checks live DNS over Cloudflare DoH.

An HTTP tunnel does not replace SMTP on port 25.

To put this on Vercel, Railway, Coolify, Openship, or a VPS, see docs/self-hosting.md.

How it fits together

  browser  →  account-scoped Next.js routes + provider-neutral mail contract
                                      │
                 ┌────────────────────┴────────────────────┐
                 │                                         │
        Redakt hosted adapter                     Gmail API adapter
          demo or JMAP                         on-demand mail + drafts
                 │                                         │
          Stalwart mail                           Google OAuth grant
                 └────────────────────┬────────────────────┘
                                      │
                         Postgres connection metadata
                      (no connected-Gmail message store)

getMailProvider(user, accountId) in lib/mail/get-provider.ts resolves a provider-neutral MailAccount, obtains credentials through its connector, and returns the shared MailProvider contract. Domain administration is a separate contract, so an external mailbox connector never inherits hosted-domain powers.

Mail views have canonical, refresh-safe URLs. Authenticated route data is loaded on the server; navigation, search controls, pagination, compose, and the persistent tab rail enhance those pages on the client.

RouteView
/mail/a/:accountId/:folderAccount-scoped inbox, unread, starred, sent, drafts, spam, trash, or archive
/mail/a/:accountId/focusOpt-in AI classification of recent inbox conversations
/mail/a/:accountId/focus/thread/:threadIdConversation opened from Focus with a Focus return path
/mail/a/:accountId/:folder/thread/:threadIdAccount-scoped conversation in its originating folder context
/mail/settings/accountsConnected mailboxes, privacy, and account lifecycle
/mail/settings/domainDomain and DNS setup

Folder query state such as search, filters, and sort order is encoded in the URL, so a copied link and a full reload render the same result.

PathRole
app/App Router pages and API routes
components/shell/Login, onboarding, inbox chrome
components/mail/Thread list and reader
components/focus/Focus classification view and states
components/ui/Shared controls (StyleX + Base UI)
lib/mail/Provider types and routing
lib/ai/AI settings, provider adapters, structured output, and validation
lib/google/Google OAuth credential and enrollment lifecycle
lib/gmail/Gmail API client, normalization, MIME, provider, and sync adapter
lib/demo/Demo store and fixture provider
lib/stalwart/JMAP client and live provider
theme/StyleX tokens
fixtures/Sample .eml files for demo
deploy/stalwart/Stalwart notes and future production config
docs/self-hosting.mdVercel, Railway, Coolify, Openship, VPS

Environment

Copy .env.example to .env. Do not commit .env.

VariableDemoLive
DEMO_MODEtrue (default)false (pnpm dev:live sets this)
BETTER_AUTH_SECRETunusedrequired
BETTER_AUTH_SECRETSunusedBetter Auth OAuth-token keyring, current first
REDAKT_ENCRYPTION_KEYSunusedseparate mailbox and AI-key keyring, current first
BETTER_AUTH_URLunusedhttp://localhost:3000
DATABASE_URLunusedPostgres URL
STALWART_URLunusedhttp://localhost:8080
STALWART_ADMIN_USERunusedwizard admin email after first boot
STALWART_ADMIN_SECRETunusedwizard admin password
MAIL_HOSTNAMEMX host shown in the DNS wizardsame; use your public mail host in production
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRETunusedenables Google sign-in and Gmail OAuth
GOOGLE_PUBSUB_TOPICunusedoptional Gmail push topic
GOOGLE_PUBSUB_AUDIENCEunusedexact authenticated push audience
GOOGLE_PUBSUB_SERVICE_ACCOUNT_EMAILunusedexact push JWT identity
MAIL_SYNC_CRON_SECRETunusedprotects daily watch renewal

Mailbox passwords are at least 8 characters (Better Auth).

Keyboard

⌥N compose, j / k move, ⌘W close tab, r reply, c compose.

Contributing

Issues and pull requests are welcome. See CONTRIBUTING.md and CODE_OF_CONDUCT.md.

License

MIT. Stalwart remains AGPL when you run it.