- Go 79.3%
- templ 19.4%
- Dockerfile 1%
- Shell 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
Conduit / ⌚ Build and Deploy (push) Successful in 1m49s
|
||
| .forgejo/workflows | ||
| cmd/conduit | ||
| deploy | ||
| internal | ||
| .env.example | ||
| .gitignore | ||
| compose.example.yaml | ||
| conduit.example.yaml | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| README.md | ||
| SECURITY.md | ||
Conduit
Conduit is a self-hosted IMAP sync daemon: it continuously copies mail from
one or more source mailboxes into a single target mailbox. Think
imapsync, but running as a long-lived service with a web dashboard, real-time
delivery via IMAP IDLE, OAuth2 support for Microsoft/Google, at-rest credential
encryption, and an automatic retry queue.
Not a webmail client — Conduit never renders or lets you read mail. It moves messages between IMAP servers on your behalf.
What it does
- Many → one aggregation. Point several source IMAP accounts at one target IMAP account (e.g. consolidate several mailboxes into your primary inbox).
- Per-account workers. Each source runs its own worker: an initial folder sync, then IMAP IDLE for real-time copying, with automatic fallback to polling (Outlook always polls). Configurable poll interval and IDLE timeout.
- Copies unseen messages. For each new message: fetch the full RFC 822 body →
APPENDto the mapped target folder → apply a configurable source action (e.g. mark seen, move, or delete) → record it for deduplication. - Deduplication. Processed messages are tracked by account + folder +
UIDVALIDITY+ UID (and Message-ID), so nothing is copied twice. Stale records are purged hourly afterqueue.purge_after. - Retry queue. Failed copies are enqueued and retried once a minute, up to
queue.max_retriesattempts. - OAuth2 sources. Outlook and Google accounts authenticate via OAuth with automatic token refresh; plain IMAP accounts use a password. All credentials are encrypted at rest.
- Web dashboard. Server-rendered (templ + htmx): account status, source account CRUD, target configuration, queue inspection, and a read-only config view.
Requirements
- One target IMAP account and one or more source IMAP/OAuth accounts.
- A database: SQLite (default, embedded, pure-Go — no external server), MySQL/MariaDB, or PostgreSQL.
- Go 1.26+ to build from source, or Docker to run the published image.
Quick start (Docker)
# 1. Generate an APP_KEY (base64-encoded 32-byte AES-256 key):
openssl rand -base64 32
# ...or, using the image:
docker run --rm --entrypoint /app/conduit \
scm.nullnetwork.cc/erestodo/conduit:latest genkey
# 2. Run the daemon:
docker run -d --name conduit \
-p 8080:8080 \
-e APP_KEY='<base64-32-byte-key>' \
-e APP_URL='http://localhost:8080' \
-e WEB_ADDRESS='0.0.0.0:8080' \
-v conduit-data:/data \
-v "$PWD/config:/config" \
scm.nullnetwork.cc/erestodo/conduit:latest
The image entrypoint runs conduit -config /config/conduit.yaml -db /data/conduit.db.
With APP_KEY/APP_URL supplied via environment variables you can run without a
config file. Then open http://localhost:8080, add a target account under
/target, and add source accounts under /accounts.
WEB_ADDRESSdefaults to127.0.0.1:8080; inside a container set it to0.0.0.0:8080so the port is reachable from the host.
Configuration
Conduit reads conduit.yaml (see conduit.example.yaml)
and/or environment variables — environment variables win. Accounts are managed
in the web UI, not in the config file.
Required
| Config key | Env var | Notes |
|---|---|---|
app_key |
APP_KEY |
base64-encoded 32-byte AES-256 key; encrypts stored credentials. Generate with conduit genkey or openssl rand -base64 32. |
app_url |
APP_URL |
public base URL of the instance; used to build OAuth redirect URIs. |
database.dsn |
DATABASE_DSN |
sqlite:///data/conduit.db, mysql://…, mariadb://…, postgresql://… (a bare path is treated as SQLite). |
Optional
| Config key | Env var | Default |
|---|---|---|
web.addr |
WEB_ADDRESS |
127.0.0.1:8080 |
log.level |
LOG_LEVEL |
info (debug/info/warn/error) |
log.format |
— | text (text/json) |
queue.max_retries |
QUEUE_MAX_RETRIES |
5 |
queue.purge_after |
QUEUE_PURGE_AFTER |
720h (30 days) |
oauth.microsoft.* |
OAUTH_MICROSOFT_CLIENT_ID / _CLIENT_SECRET / _TENANT |
— |
oauth.google.* |
OAUTH_GOOGLE_CLIENT_ID / _CLIENT_SECRET |
— |
| — | TZ |
system TZ |
Authentication (web UI login)
Conduit can protect its own dashboard with OIDC login — it acts as an OpenID
Connect Relying Party and works with any provider that supports discovery
(Authelia, Authentik, Keycloak, Pocket ID, …). Enable it by setting the
auth block (or AUTH_* env vars):
| Config key | Env var | Notes |
|---|---|---|
auth.issuer |
AUTH_ISSUER |
OIDC issuer / discovery base URL. |
auth.client_id |
AUTH_CLIENT_ID |
|
auth.client_secret |
AUTH_CLIENT_SECRET |
|
auth.scopes |
AUTH_SCOPES |
default openid profile email; add your provider's groups scope to receive the groups claim. Comma- or space-separated. |
auth.groups_claim |
AUTH_GROUPS_CLAIM |
ID-token claim holding groups/roles (default groups). |
auth.allowed_emails |
AUTH_ALLOWED_EMAILS |
allowlist; empty ⇒ any authenticated user. |
auth.allowed_groups |
AUTH_ALLOWED_GROUPS |
allowlist by group/role. |
auth.session_ttl |
AUTH_SESSION_TTL |
session cookie lifetime (default 12h). |
- Redirect URI to register at your provider:
<APP_URL>/auth/callback. - Sessions are a stateless, AES-256-GCM-encrypted cookie signed with
APP_KEY— no database changes. - If
authis not configured, Conduit does no in-app auth (front it with a trusted proxy).
Reverse proxy: if you previously gated Conduit with proxy forward-auth (e.g.
Authelia), remove it once OIDC login is enabled, and ensure /oauth/callback
and /auth/callback are always reachable without proxy auth. The
mail-account OAuth callback is a cross-site POST that can't carry a session
cookie and authenticates via its own state token; gating it at the proxy breaks
the mail-account authorization flow.
Build from source
go build -o conduit ./cmd/conduit
./conduit genkey # generate an APP_KEY
./conduit -config conduit.yaml # run
The web UI is generated from .templ files with templ:
go generate ./... (or templ generate) after editing anything under
internal/web/templates.
Deployment
- Docker —
deploy/docker/Dockerfile, a multi-stage build on Docker Hardened Images producing a distroless, nonroot image. - systemd —
deploy/systemd/conduit.service, a heavily sandboxed unit. - OpenRC —
deploy/openrc/conduit.
CI lives in .forgejo/workflows and publishes container
images to scm.nullnetwork.cc/erestodo/conduit.
Architecture
| Path | Responsibility |
|---|---|
cmd/conduit |
Entrypoint, genkey, config load, starts supervisor + web server. |
internal/daemon |
Supervisor, per-account workers, retry loop, purge loop, OAuth token refresh. |
internal/imaputil |
IMAP client, IDLE, fetch, folder mapping, source actions. |
internal/db |
Repositories and migrations (SQLite / MySQL / PostgreSQL). |
internal/web |
chi HTTP server, handlers, and templ (htmx) templates. |
internal/{config,crypto,oauth,mailutil,logutil,durationfmt} |
Support packages. |
Security
Account credentials and OAuth tokens are encrypted at rest with AES-256 using
APP_KEY; Conduit only connects to the IMAP servers you configure (and, when
OAuth is enabled, the Microsoft/Google OAuth endpoints); there is no telemetry.
See SECURITY.md.
License
MIT — see LICENSE.