An email client & provider gateway so you can build your best email experience.
211
stars
61
commits
TypeScript
primary language
Sep 8, 2026
updated
Desktop-focused email, backed by a provider-agnostic Inbox SDK. Bring your mailboxes into one unified inbox while keeping their identities, credentials, and provider capabilities separate.

Requires Bun 1.4+.
bun --no-env-file install
bun --no-env-file run start
The first run starts two fictional mailboxes through the real Inbox SDK. No provider credentials, OAuth setup, OpenCan, or machine-specific services are needed. Ctrl-C stops both the client and local host.
start builds and serves the optimized client locally. Use bun --no-env-file run dev for hot-reloading development; development-mode React diagnostics add overhead on large mailboxes. Both commands keep the same local-only host, sessions, and provider configuration.
Run docker compose up -d --build --wait, then open http://localhost:5178.
The image contains the built app, not your configuration or mail. On first run,
the app creates a fictional installation in the named volume superlocal-state:
/persist/superlocal.local.json
/persist/data/mock/ # Fictional mail, databases and generated keys
/persist/data/real/ # Created when real mode is selected
Each mode keeps host.sqlite, its mail database(s), runtime-secrets.json and
SQLite journals together. Configure real providers in the retained config as
described below; never replace its instance ID or keys during an update.
Google OAuth client secrets can be supplied through the same explicit environment
variables used locally. They are runtime inputs, not image build arguments.
After updating the checkout, run docker compose up -d --build --wait again.
Compose replaces the app container and reattaches the same volume. A hosting
platform's Git-triggered redeploy must likewise retain this volume at /persist;
the GitHub workflow below publishes images but does not restart a remote host.
docker compose down retains the volume; do not use down -v or delete the
volume when updating. Back up config, databases and keys together with the app
stopped. Existing Mac installations are not imported automatically.
Set SUPERLOCAL_DOCKER_PORT to use another local port and SUPERLOCAL_VOLUME_NAME
only for a deliberately separate installation. Do not run two app instances on
the same volume. Named volumes are initialized for the image's non-root bun
user; a bind-mount replacement must be owned by that user's UID/GID (1000:1000),
with private directories and 0600 config/key files.
Only the web port is published, on host loopback. The backend stays inside the container. Public hosting/authentication is unchanged. Browser-local settings and recovery copies remain in the browser; this volume preserves server state.
Pushes to main build and publish Linux AMD64 and ARM64 images at
ghcr.io/r44vc0rp/superlocal:latest. The workflow can also be run manually on
main. It uses GitHub's built-in GITHUB_TOKEN with package-write permission;
no Docker Hub account or registry password is needed. Images also receive a
sha-<full-commit-sha> tag, and the workflow records the digest for pinned deploys.
To run or update the published image without building it locally:
SUPERLOCAL_IMAGE=ghcr.io/r44vc0rp/superlocal:latest \
docker compose up -d --no-build --pull always --wait
The same superlocal-state volume is retained. Set SUPERLOCAL_IMAGE to a
commit-specific tag or digest to select a particular version; database migrations
may still limit downgrades. Other container hosts can use the same image and mount
/persist without Compose. Server restarts/webhooks remain host-specific.
GHCR packages initially default to private, even for a public repository. The
package owner must set its visibility to public once for anonymous pulls. A
private package instead requires a GitHub token with read:packages on the
deployment host. This workflow never includes runtime mail, keys or config in
the image, and does not change the application's local-only authentication.


The screenshots use only the included fictional mock data, at a slightly enlarged viewing scale.
The goal is ready-to-connect providers out of the box: choose a service, complete its authorization, and select mailboxes in the same UI. You will still need the provider account, required permissions, and any domain/DNS setup that service requires.
| Provider | Current state | Direction |
|---|---|---|
| Mock mailboxes | Included and offline | Keep the full app usable immediately, without personal credentials. |
| Gmail | Implemented; host OAuth client configuration required | Make authorization, reconnects, and multi-account setup simpler. |
| Inbound.new | Implemented; API-key onboarding and domain/address views | Improve large-domain onboarding, import progress, and recovery within the provider's sync limits. |
| iCloud / IMAP | In progress | Email + app-specific-password setup for iCloud, secure IMAP/SMTP presets, and end-to-end provider qualification. |
| Resend | Planned | Add a sending/receiving adapter using received-email APIs, attachment retrieval, and verified webhook ingestion. |
| Cloudflare Email Service | Planned | Bridge Email Routing / Email Workers into durable SDK mail storage, and integrate supported sending APIs or SMTP. |
Resend and Cloudflare support email workflows, but that does not automatically give them traditional IMAP folders or native read flags. Their adapters should expose real capabilities while the SDK supplies reusable local workflows.
For current unified-inbox decisions, import limits, and open product questions, see unified-inbox-decisions.md. Apple's separate OAuth opportunity is recorded in apple-provider.ind.
Client → Inbox SDK APIs → provider adapters
| Package | Responsibility |
|---|---|
apps/web | React client: inbox views, reading, composing, and settings. |
apps/local-host | Application sessions, provider onboarding, runtime paths, and connection policy. |
packages/inbox-sdk | Normalized mail contracts, SQLite storage, encrypted credentials, queries, jobs, drafts, events, and provider translation. |
apps/mock-api | A fictional upstream and real InboxProvider implementation, not a fake SDK HTTP layer. |
The SDK currently runs on Bun and SQLite. The included host is local-only; public deployment authentication and team access controls are not implemented.
The current Gmail and Inbound connectors use the local host configuration:
superlocal.local.json.mode to real and enable providers.gmail.enabled and/or providers.inbound.enabled.http://localhost:5178/v1/oauth/google/callback for the default local setup. Google does not accept .local redirect domains; use the configured localhost origin for local authorization.Provider credentials are submitted to the host, encrypted per connection by the SDK, and not returned by the mail APIs. Mock and real modes stay separate. The Gmail OAuth defaults reference SUPERLOCAL_GOOGLE_CLIENT_ID and SUPERLOCAL_GOOGLE_CLIENT_SECRET; export them explicitly, or configure the corresponding providers.gmail.oauth values as private strings or { "env": "YOUR_VARIABLE_NAME" } references. No SDK .env file or unrelated ambient credentials are imported.
Real connections default to normal mail access (allowProviderWrites.real: true). For an optional read-only host, set it to false; Gmail can use https://www.googleapis.com/auth/gmail.readonly with openid and email rather than modify/send scopes. Reauthorize old read-only grants before sending or modifying mail. Provider capabilities and OAuth permissions still determine which native actions are available.
Keep the generated instanceId with its data. Runtime databases, encryption keys, and session keys live outside the checkout:
| Platform | Default location |
|---|---|
| macOS | ~/Library/Application Support/superlocal/<instanceId>/ |
| Linux | $XDG_DATA_HOME/superlocal/<instanceId>/, defaulting to ~/.local/share |
| Windows | %LOCALAPPDATA%/superlocal/<instanceId>/ |
dataDir can point to another private directory. Back up the whole instance together: missing or mismatched keys fail closed and are never silently regenerated. Old pilot databases and .env.local credentials are not imported automatically.
The launcher serves the client on port 5178 and the SDK host on 8790. web.port, backend.port, web.origin, and web.allowedOrigins control the local addresses. A null web.origin uses http://localhost:<web.port>. An existing OpenCan setup can expose https://super.local; Superlocal does not install or manage OpenCan.
For an isolated run, set SUPERLOCAL_CONFIG to a new configuration path and SUPERLOCAL_DATA_DIR to a separate private directory. The configuration's parent directory must exist. SUPERLOCAL_WEB_PORT, SUPERLOCAL_API_PORT, and SUPERLOCAL_WEB_ORIGIN override addresses. bun --no-env-file run dev:host starts only the backend.
From the repository root:
bun --no-env-file run test:web
INBOX_TEST_LIVE=false bun --no-env-file run test:api
bun --no-env-file run typecheck
bun --no-env-file run build
Live-provider qualification is separate from deterministic tests. Never commit credentials, private configuration, runtime databases, real mail, or private diagnostic captures.
61 commits
Hacker News (1)
TypeScript
94.7%
CSS
4.8%
An email client & provider gateway so you can build your best email experience.
211
stars
61
commits
TypeScript
primary language
Sep 8, 2026
updated
Desktop-focused email, backed by a provider-agnostic Inbox SDK. Bring your mailboxes into one unified inbox while keeping their identities, credentials, and provider capabilities separate.

Requires Bun 1.4+.
bun --no-env-file install
bun --no-env-file run start
The first run starts two fictional mailboxes through the real Inbox SDK. No provider credentials, OAuth setup, OpenCan, or machine-specific services are needed. Ctrl-C stops both the client and local host.
start builds and serves the optimized client locally. Use bun --no-env-file run dev for hot-reloading development; development-mode React diagnostics add overhead on large mailboxes. Both commands keep the same local-only host, sessions, and provider configuration.
Run docker compose up -d --build --wait, then open http://localhost:5178.
The image contains the built app, not your configuration or mail. On first run,
the app creates a fictional installation in the named volume superlocal-state:
/persist/superlocal.local.json
/persist/data/mock/ # Fictional mail, databases and generated keys
/persist/data/real/ # Created when real mode is selected
Each mode keeps host.sqlite, its mail database(s), runtime-secrets.json and
SQLite journals together. Configure real providers in the retained config as
described below; never replace its instance ID or keys during an update.
Google OAuth client secrets can be supplied through the same explicit environment
variables used locally. They are runtime inputs, not image build arguments.
After updating the checkout, run docker compose up -d --build --wait again.
Compose replaces the app container and reattaches the same volume. A hosting
platform's Git-triggered redeploy must likewise retain this volume at /persist;
the GitHub workflow below publishes images but does not restart a remote host.
docker compose down retains the volume; do not use down -v or delete the
volume when updating. Back up config, databases and keys together with the app
stopped. Existing Mac installations are not imported automatically.
Set SUPERLOCAL_DOCKER_PORT to use another local port and SUPERLOCAL_VOLUME_NAME
only for a deliberately separate installation. Do not run two app instances on
the same volume. Named volumes are initialized for the image's non-root bun
user; a bind-mount replacement must be owned by that user's UID/GID (1000:1000),
with private directories and 0600 config/key files.
Only the web port is published, on host loopback. The backend stays inside the container. Public hosting/authentication is unchanged. Browser-local settings and recovery copies remain in the browser; this volume preserves server state.
Pushes to main build and publish Linux AMD64 and ARM64 images at
ghcr.io/r44vc0rp/superlocal:latest. The workflow can also be run manually on
main. It uses GitHub's built-in GITHUB_TOKEN with package-write permission;
no Docker Hub account or registry password is needed. Images also receive a
sha-<full-commit-sha> tag, and the workflow records the digest for pinned deploys.
To run or update the published image without building it locally:
SUPERLOCAL_IMAGE=ghcr.io/r44vc0rp/superlocal:latest \
docker compose up -d --no-build --pull always --wait
The same superlocal-state volume is retained. Set SUPERLOCAL_IMAGE to a
commit-specific tag or digest to select a particular version; database migrations
may still limit downgrades. Other container hosts can use the same image and mount
/persist without Compose. Server restarts/webhooks remain host-specific.
GHCR packages initially default to private, even for a public repository. The
package owner must set its visibility to public once for anonymous pulls. A
private package instead requires a GitHub token with read:packages on the
deployment host. This workflow never includes runtime mail, keys or config in
the image, and does not change the application's local-only authentication.


The screenshots use only the included fictional mock data, at a slightly enlarged viewing scale.
The goal is ready-to-connect providers out of the box: choose a service, complete its authorization, and select mailboxes in the same UI. You will still need the provider account, required permissions, and any domain/DNS setup that service requires.
| Provider | Current state | Direction |
|---|---|---|
| Mock mailboxes | Included and offline | Keep the full app usable immediately, without personal credentials. |
| Gmail | Implemented; host OAuth client configuration required | Make authorization, reconnects, and multi-account setup simpler. |
| Inbound.new | Implemented; API-key onboarding and domain/address views | Improve large-domain onboarding, import progress, and recovery within the provider's sync limits. |
| iCloud / IMAP | In progress | Email + app-specific-password setup for iCloud, secure IMAP/SMTP presets, and end-to-end provider qualification. |
| Resend | Planned | Add a sending/receiving adapter using received-email APIs, attachment retrieval, and verified webhook ingestion. |
| Cloudflare Email Service | Planned | Bridge Email Routing / Email Workers into durable SDK mail storage, and integrate supported sending APIs or SMTP. |
Resend and Cloudflare support email workflows, but that does not automatically give them traditional IMAP folders or native read flags. Their adapters should expose real capabilities while the SDK supplies reusable local workflows.
For current unified-inbox decisions, import limits, and open product questions, see unified-inbox-decisions.md. Apple's separate OAuth opportunity is recorded in apple-provider.ind.
Client → Inbox SDK APIs → provider adapters
| Package | Responsibility |
|---|---|
apps/web | React client: inbox views, reading, composing, and settings. |
apps/local-host | Application sessions, provider onboarding, runtime paths, and connection policy. |
packages/inbox-sdk | Normalized mail contracts, SQLite storage, encrypted credentials, queries, jobs, drafts, events, and provider translation. |
apps/mock-api | A fictional upstream and real InboxProvider implementation, not a fake SDK HTTP layer. |
The SDK currently runs on Bun and SQLite. The included host is local-only; public deployment authentication and team access controls are not implemented.
The current Gmail and Inbound connectors use the local host configuration:
superlocal.local.json.mode to real and enable providers.gmail.enabled and/or providers.inbound.enabled.http://localhost:5178/v1/oauth/google/callback for the default local setup. Google does not accept .local redirect domains; use the configured localhost origin for local authorization.Provider credentials are submitted to the host, encrypted per connection by the SDK, and not returned by the mail APIs. Mock and real modes stay separate. The Gmail OAuth defaults reference SUPERLOCAL_GOOGLE_CLIENT_ID and SUPERLOCAL_GOOGLE_CLIENT_SECRET; export them explicitly, or configure the corresponding providers.gmail.oauth values as private strings or { "env": "YOUR_VARIABLE_NAME" } references. No SDK .env file or unrelated ambient credentials are imported.
Real connections default to normal mail access (allowProviderWrites.real: true). For an optional read-only host, set it to false; Gmail can use https://www.googleapis.com/auth/gmail.readonly with openid and email rather than modify/send scopes. Reauthorize old read-only grants before sending or modifying mail. Provider capabilities and OAuth permissions still determine which native actions are available.
Keep the generated instanceId with its data. Runtime databases, encryption keys, and session keys live outside the checkout:
| Platform | Default location |
|---|---|
| macOS | ~/Library/Application Support/superlocal/<instanceId>/ |
| Linux | $XDG_DATA_HOME/superlocal/<instanceId>/, defaulting to ~/.local/share |
| Windows | %LOCALAPPDATA%/superlocal/<instanceId>/ |
dataDir can point to another private directory. Back up the whole instance together: missing or mismatched keys fail closed and are never silently regenerated. Old pilot databases and .env.local credentials are not imported automatically.
The launcher serves the client on port 5178 and the SDK host on 8790. web.port, backend.port, web.origin, and web.allowedOrigins control the local addresses. A null web.origin uses http://localhost:<web.port>. An existing OpenCan setup can expose https://super.local; Superlocal does not install or manage OpenCan.
For an isolated run, set SUPERLOCAL_CONFIG to a new configuration path and SUPERLOCAL_DATA_DIR to a separate private directory. The configuration's parent directory must exist. SUPERLOCAL_WEB_PORT, SUPERLOCAL_API_PORT, and SUPERLOCAL_WEB_ORIGIN override addresses. bun --no-env-file run dev:host starts only the backend.
From the repository root:
bun --no-env-file run test:web
INBOX_TEST_LIVE=false bun --no-env-file run test:api
bun --no-env-file run typecheck
bun --no-env-file run build
Live-provider qualification is separate from deterministic tests. Never commit credentials, private configuration, runtime databases, real mail, or private diagnostic captures.
Hacker News (1)
61 commits
TypeScript
94.7%
CSS
4.8%