superlocal README

The project's README from github.com/R44VC0RP/superlocal. As one markdown file: README.md.

Superlocal

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.

Superlocal inbox demo

Run locally

Requires Bun 1.4+.

  1. Install dependencies:
    bun --no-env-file install
    
  2. Start the app:
     bun --no-env-file run start
    
  3. Open http://localhost:5178.

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.

Docker with persistent storage

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. Browser-local settings and recovery copies remain in the browser; this volume preserves server state.

Published images

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.

Restricted Google access

Google is the application login provider for restricted installations. It reuses the same Google OAuth client ID and secret configured for Gmail, even when the Gmail mailbox provider is disabled. Existing local/demo installations retain their explicit loopback mode; adding Google credentials alone does not change their access policy.

To enable the gate, set these runtime values (or set auth.method, auth.allowedEmails and web.origin in the retained configuration):

SUPERLOCAL_AUTH_METHOD=google
[email protected],[email protected]
SUPERLOCAL_WEB_ORIGIN=https://mail.example.com
SUPERLOCAL_GOOGLE_CLIENT_ID=your-google-web-client-id
SUPERLOCAL_GOOGLE_CLIENT_SECRET=your-google-web-client-secret

For a real remote mailbox setup, also select mode: "real" and enable the desired providers in the retained config, as described under Connect real providers. Mock mode remains an offline demo, not a connection to real mail services.

Register both redirect URIs on that Google OAuth web client:

https://mail.example.com/api/auth/callback/google      # Application login
https://mail.example.com/v1/oauth/google/callback     # Optional Gmail connection

Application login requests only identity/profile scopes and checks Google's verified email against the exact allowlist. An empty list denies everyone; wildcards and whole-domain entries are not supported. Case and surrounding whitespace normalize, but plus tags and Gmail dots are not rewritten. Restart after changing the list: removed users' existing sessions then fail the access check. Sign out is available in the sidebar footer.

Each approved person has a private account on the installation. Their first Google sign-in starts with no connected mailboxes. They can connect multiple mailboxes across the enabled providers; connections, mail, drafts and settings are scoped to that person. There are no shared-mailbox or team roles. Google login does not connect Gmail or grant mailbox scopes. Login provider tokens are not retained after identity verification, and mailbox credentials stay in the SDK.

Browser-held preferences, draft recovery and issue reports are also user-scoped. Switching the signed-in person loads a fresh application context; stale tabs and requests cannot operate under the next person's session. Existing unscoped local browser data and legacy loopback mail are not imported or exposed to Google users. Configure a fresh remote installation and reconnect the desired providers; this feature does not migrate an existing local installation.

Better Auth stores login identities, durable private-owner bindings and sessions in auth.sqlite beside the mode's other databases. Its secret is derived from the retained runtime session key, so /persist also preserves login state and user ownership across redeploys. Missing Google credentials fail closed in Google mode; it never falls back to local access. Only the login shell/assets, auth flow and health check are public. Mail, settings, attachments, authenticated images and event streams require an approved session. Use HTTPS for a public origin; loopback mode still rejects public origins and production startup. TLS/reverse-proxy setup remains external. Login starts share a conservative installation-wide limit of 20 per minute; forwarded client-IP headers are not trusted. For public exposure, apply per-client limits at the trusted reverse proxy so one caller cannot exhaust that shared budget. Removing an email denies its existing sessions while it is off the list; re-adding it can restore those sessions until their absolute expiry or sign-out.

One inbox, separate mailboxes

See the reader and mailbox settings

Reading a conversation

A conversation from the fictional mock inbox

Choosing mailboxes and shortcuts

Per-user unified inbox selection and pinned mailbox shortcuts

The screenshots use only the included fictional mock data, at a slightly enlarged viewing scale.

Providers and roadmap

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.

What we are working toward

  1. Less setup outside the app. Provider presets, guided authorization, connection checks, and useful reconnect errors instead of routine configuration-file editing.
  2. More interchangeable providers. Finish IMAP/iCloud support, then add Resend and Cloudflare without introducing separate frontend mail APIs.
  3. Better large-inbox progress. Keep cached mail usable while imports run, show meaningful progress and partial failures, and extend paging as mailbox collections grow.
  4. Personalized Important / Other. Add optional per-user learning from explicit corrections, with reversible feedback and conservative handling of uncertain mail.

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.

Architecture

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 host supports explicit local-only sessions or allowlisted Google sign-in with private per-user mailboxes and settings. Shared-mailbox and team roles are not implemented.

Connect real providers

The current Gmail and Inbound connectors use the local host configuration:

  1. Stop the app and open the generated, git-ignored superlocal.local.json.
  2. Set mode to real and enable providers.gmail.enabled and/or providers.inbound.enabled.
  3. For Gmail, configure the host's Google OAuth web client. Register 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.
  4. Restart the app and open Settings → Add Accounts. Inbound takes an API key, then offers discovered mailboxes. Gmail uses the host-managed OAuth flow.
  5. Use Settings → Mailboxes to choose unified inclusion and pinned shortcuts.

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.

Runtime storage and advanced local configuration

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.

Development

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.

superlocal (R44VC0RP/superlocal)

Searching Database…

这是什么?Superlocal 是一个面向桌面的邮件客户端,背后由一个与提供商无关的 Inbox SDK 支撑。它让你把来自不同提供商(Gmail、Inbound.new、模拟)的邮箱统一到一个收件箱,同时保持每个账号的身份、凭据和提供商能力相互独立。
它是托管服务,还是我自己运行的东西?它是一个自托管应用,带有本地 SQLite 支撑的宿主和 React Web 客户端,可以用 Bun 在本地运行,也可以通过带持久化存储的 Docker 运行——不是托管的 SaaS 收件箱。
这个怎么运行?本地方式:用 Bun 1.4+ 安装依赖,然后运行启动脚本并打开本地 URL。首次运行会自动创建两个虚构的模拟邮箱。或者,运行 docker compose up -d --build --wait 获得带持久化存储的 Docker 化部署。
试用之前我需要提供商的凭据吗?不需要——模拟模式开箱即用,使用虚构邮箱且无需凭据。要连接真实的 Gmail 或 Inbound.new 账号,你需要编辑生成的 superlocal.local.json,把 mode 设为 'real',启用相应提供商,并提供 Google OAuth 客户端 ID/密钥或 Inbound API 密钥。
目前哪些提供商真正可用?模拟邮箱和 Gmail 已实现,Inbound.new 已实现并支持 API 密钥接入;iCloud/IMAP 仍在开发中,Resend 和 Cloudflare Email Service 仅在计划中。目前还不支持共享邮箱或团队角色。
它支持多个经过批准的用户,还是单用户?在受限的多用户设置下,它支持白名单式的 Google 登录,每个用户有私有的邮箱和设置,但没有共享邮箱或团队角色。
服务器运行在什么平台上?发布的 Docker 镜像面向 Linux AMD64 和 ARM64 构建;本地只需要 Bun,因此也能在 macOS 和 Windows 开发机上运行。
我的邮件和配置会留在我的机器上吗?会——在 Docker 中,所有状态(host.sqlite、邮件数据库、runtime-secrets.json)都存放在本地命名卷中,绝不会打进镜像;提供商凭据按连接加密,且绝不会由邮件 API 返回。
我怎么在统一收件箱和各个邮箱之间切换?每个添加的邮箱默认都会加入统一视图;用“设置 → 邮箱”来挑选一个更小的子集。Ctrl+0 打开统一收件箱,Ctrl+1–9 跳转到按顺序排列的已固定邮箱。
它如何保证收到的邮件查看起来是安全的?收到的 HTML 在禁用脚本的 iframe 中渲染,SDK 会对内容进行净化,远程媒体只通过经过身份验证的路由提供,并拦截已知跟踪器。
代码库是如何组织的?它分为 apps/web(React 客户端)、apps/local-host(会话和提供商接入)、packages/inbox-sdk(邮件契约、存储、加密凭据)和 apps/mock-api(实现真实 InboxProvider 接口的假上游,用于演示)。
更新 Docker 镜像时我的数据会怎样?再次运行 docker compose up -d --build --wait 会替换容器,但会重新挂载同一个 superlocal-state 卷;你绝不要运行 docker compose down -v 或删除该卷,否则会清空你的邮件、密钥和配置。
What are similar projects?crm, laya, whirl, freellmapi, omamail, snagtime, spacedrive, executor
Creator
R44VC0RP
Size
58.8 MB
Stars
229
Updated
CommitSep 08
Age
1mo
Commits · 90 Days