A Go single-binary daemon that bridges mobile devices (Android / iOS) speaking the Exchange ActiveSync (EAS) 16.1 protocol to a plain IMAP backend. It has no web UI and is meant to sit behind a reverse proxy.
The roles are fixed:
- IMAP is the source of truth — the daemon never owns mail state; it reflects what the IMAP server holds.
- The daemon is the translation layer — it speaks EAS to devices and IMAP/SMTP/DAV to the server, reconciling the two models.
- ActiveSync is the transport lane to and from the devices.
There is a single backend for every user (like Z-Push — no per-tenant or per-domain routing). A device authenticates with whatever username its owner typed, and that string is sent to the backend verbatim. Mail is always on; Contacts (CardDAV), Calendar/Reminders (CalDAV), and Notes (IMAP) are optional modules. Device passwords arrive at runtime and live only in an in-memory vault — they never touch disk.
The implementation is written to be faithful to primary specifications rather than reverse-engineered behavior:
- Exchange ActiveSync — the Microsoft Open Specifications:
[MS-ASWBXML](WBXML),[MS-ASHTTP](transport),[MS-ASCMD](commands),[MS-ASEMAIL],[MS-ASPROV](provisioning),[MS-ASDTYPE](base types), and the module specs[MS-ASCNTC],[MS-ASCAL],[MS-ASTASK],[MS-ASNOTE]. - IMAP — RFC 3501 / RFC 9051 (IMAP4rev1/rev2), RFC 7162 (CONDSTORE/QRESYNC), RFC 5465 (NOTIFY), RFC 2177 (IDLE), RFC 6154 (SPECIAL-USE), RFC 6203 (SEARCH=FUZZY).
- SMTP / DAV / MIME — RFC 5321 / RFC 6409 (SMTP submission), RFC 5322 (IMF), RFC 6352 (CardDAV), RFC 4791 (CalDAV), RFC 5545 (iCalendar), RFC 6350 (vCard).
The MS-AS* specs are published in the Microsoft Open Specifications, and the RFCs by the RFC Editor. Microsoft's Remote Connectivity Analyzer (O365 EAS test) is the external acceptance check.
Config layers in increasing precedence:
defaults < process env (IMAP_ACTIVESYNC_*) < .env file < config file
Every scalar setting, the module toggles, and the backend are all
env-overridable, so a container needs no config file at all. To use a file,
copy imap-activesync.example.yaml to
/etc/imap-activesync.yaml (the default path) or pass -config <path>. The file
holds no secrets.
Minimum to get running — point it at your IMAP (and optionally SMTP) backend:
listen: "127.0.0.1:9000" # behind a reverse proxy (plain HTTP)
data_dir: "/var/lib/imap-activesync/state.db"
backend:
imap_address: "imap.example.com:993" # implicit TLS
imap_starttls: false
smtp_address: "smtp.example.com:465" # outbound (optional)
smtp_starttls: falseThe equivalent environment variables are IMAP_ACTIVESYNC_LISTEN,
IMAP_ACTIVESYNC_DATA_DIR, IMAP_ACTIVESYNC_IMAP_ADDRESS,
IMAP_ACTIVESYNC_IMAP_STARTTLS, IMAP_ACTIVESYNC_SMTP_ADDRESS, etc. See the
example file for the full set, including the optional modules, garbage
collection, Ping heartbeat bounds, and provisioning.
Multi-arch images (linux/amd64, linux/arm64) are published to the GitHub
Container Registry on each release. The image is built on Chainguard's
distroless static base and runs nonroot (uid 65532).
docker run -d --name imap-activesync \
-p 9000:9000 \
-v imap-activesync-data:/data \
-e IMAP_ACTIVESYNC_IMAP_ADDRESS=imap.example.com:993 \
-e IMAP_ACTIVESYNC_SMTP_ADDRESS=smtp.example.com:465 \
ghcr.io/user00265/imap-activesync:latestA compose.yaml is included. The container writes its bbolt
state to /data; use a named volume (as shown) so the mount inherits the
nonroot ownership automatically.
For a host install, drop the binary in place and use one of the provided units in
deploy/systemd/ — a hardened system-wide unit (DynamicUser,
StateDirectory=/var/lib/imap-activesync) or a per-user unit. Each unit's header
documents the exact install steps. In short:
install -Dm755 imap-activesync /usr/local/bin/imap-activesync
install -Dm644 imap-activesync.example.yaml /etc/imap-activesync.yaml # then edit
install -Dm644 deploy/systemd/imap-activesync.service \
/etc/systemd/system/imap-activesync.service
systemctl daemon-reload && systemctl enable --now imap-activesyncThis project was developed with the assistance of AI large language models (LLMs). What that means in practice: an LLM was used as a coding and drafting aid — generating, refactoring, and reviewing code, and helping cross-check behavior against the specifications listed above. It does not mean the output is unreviewed or authoritative: every change is subject to human review, the test suite is spec-first (the spec is the ground truth, never the model's output), and the specifications and RFCs remain the final word wherever the two disagree.
Released under the MIT License.
Copyright (c) 2026 Elisamuel "Sam" Resto Donate sam@samresto.dev