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 devOpen 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:livedev: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.
| Service | Port |
|---|---|
| Redakt | 3000 |
| Postgres | 5432 |
| Stalwart HTTP / JMAP | 8080 |
| SMTP / SMTPS / IMAPS | 25 / 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.
| Route | View |
|---|---|
/mail/a/:accountId/:folder | Account-scoped inbox, unread, starred, sent, drafts, spam, trash, or archive |
/mail/a/:accountId/focus | Opt-in AI classification of recent inbox conversations |
/mail/a/:accountId/focus/thread/:threadId | Conversation opened from Focus with a Focus return path |
/mail/a/:accountId/:folder/thread/:threadId | Account-scoped conversation in its originating folder context |
/mail/settings/accounts | Connected mailboxes, privacy, and account lifecycle |
/mail/settings/domain | Domain 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.
| Path | Role |
|---|---|
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.md | Vercel, Railway, Coolify, Openship, VPS |
Environment
Copy .env.example to .env. Do not commit .env.
| Variable | Demo | Live |
|---|---|---|
DEMO_MODE | true (default) | false (pnpm dev:live sets this) |
BETTER_AUTH_SECRET | unused | required |
BETTER_AUTH_SECRETS | unused | Better Auth OAuth-token keyring, current first |
REDAKT_ENCRYPTION_KEYS | unused | separate mailbox and AI-key keyring, current first |
BETTER_AUTH_URL | unused | http://localhost:3000 |
DATABASE_URL | unused | Postgres URL |
STALWART_URL | unused | http://localhost:8080 |
STALWART_ADMIN_USER | unused | wizard admin email after first boot |
STALWART_ADMIN_SECRET | unused | wizard admin password |
MAIL_HOSTNAME | MX host shown in the DNS wizard | same; use your public mail host in production |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET | unused | enables Google sign-in and Gmail OAuth |
GOOGLE_PUBSUB_TOPIC | unused | optional Gmail push topic |
GOOGLE_PUBSUB_AUDIENCE | unused | exact authenticated push audience |
GOOGLE_PUBSUB_SERVICE_ACCOUNT_EMAIL | unused | exact push JWT identity |
MAIL_SYNC_CRON_SECRET | unused | protects 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.