Modern imapsync-like tool to sync multiple email accounts to one single IMAP account
  • Go 79.3%
  • templ 19.4%
  • Dockerfile 1%
  • Shell 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Elisamuel Resto a06e2192d5
All checks were successful
Conduit / ⌚ Build and Deploy (push) Successful in 1m49s
fix(daemon): mark polling accounts connected/polling so UI isn't stuck on "connecting"
2026-08-07 02:26:44 -05:00
.forgejo/workflows Restructure: rename module to conduit, Forgejo CI, DHI image, accurate docs 2026-08-06 23:44:29 -05:00
cmd/conduit feat(auth): OIDC web-UI login (Authelia/Authentik/Keycloak/Pocket ID) + example compose 2026-08-07 00:50:31 -05:00
deploy fix: track cmd/conduit and openrc script hidden by .gitignore 2026-08-06 23:51:54 -05:00
internal fix(daemon): mark polling accounts connected/polling so UI isn't stuck on "connecting" 2026-08-07 02:26:44 -05:00
.env.example feat(config): add AUTH_SCOPES env var so OIDC scopes (incl. groups) are settable without a config file 2026-08-07 01:00:50 -05:00
.gitignore fix: track cmd/conduit and openrc script hidden by .gitignore 2026-08-06 23:51:54 -05:00
compose.example.yaml feat(config): add AUTH_SCOPES env var so OIDC scopes (incl. groups) are settable without a config file 2026-08-07 01:00:50 -05:00
conduit.example.yaml feat(auth): OIDC web-UI login (Authelia/Authentik/Keycloak/Pocket ID) + example compose 2026-08-07 00:50:31 -05:00
go.mod feat(auth): OIDC web-UI login (Authelia/Authentik/Keycloak/Pocket ID) + example compose 2026-08-07 00:50:31 -05:00
go.sum feat(auth): OIDC web-UI login (Authelia/Authentik/Keycloak/Pocket ID) + example compose 2026-08-07 00:50:31 -05:00
LICENSE Restructure: rename module to conduit, Forgejo CI, DHI image, accurate docs 2026-08-06 23:44:29 -05:00
README.md feat(config): add AUTH_SCOPES env var so OIDC scopes (incl. groups) are settable without a config file 2026-08-07 01:00:50 -05:00
SECURITY.md Restructure: rename module to conduit, Forgejo CI, DHI image, accurate docs 2026-08-06 23:44:29 -05:00

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 → APPEND to 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 after queue.purge_after.
  • Retry queue. Failed copies are enqueued and retried once a minute, up to queue.max_retries attempts.
  • 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_ADDRESS defaults to 127.0.0.1:8080; inside a container set it to 0.0.0.0:8080 so 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 auth is 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

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.