# Development ## Architecture ``` src/ main.rs CLI (serve, admin, config, device, probe) lib.rs public router (ActiveSync, Autodiscover, /, /healthz) app.rs application state: settings (ArcSwap), IMAP pool, metrics, lockouts config.rs settings model (serde, validation, merging, secrets) db.rs SQLite (sqlx, WAL), migrations in migrations/, housekeeping server.rs HTTP/1.1 + HTTP/2 listener, optional TLS, graceful shutdown wbxml/ WBXML encoder/decoder + MS-ASWBXML code pages eas/ ActiveSync protocol mod.rs request parsing (plain/base64 query), auth, provisioning, dispatcher state.rs SyncKey {uuid}N and states (item fingerprints) folders.rs FolderSync / Create / Update / Delete sync.rs Sync, GetItemEstimate, MoveItems ping.rs Ping and the shared wait-for-changes loop (hanging Sync) provision.rs policies, remote wipe mailcmd.rs SendMail / SmartReply / SmartForward / MeetingResponse itemops.rs ItemOperations, GetAttachment misc.rs Settings, Search, ResolveRecipients convert/ RFC 5322 / iCalendar / vCard <-> ApplicationData backend/ imap.rs own async IMAP client (literals, UIDPLUS, MOVE, SPECIAL-USE) dav.rs CalDAV/CardDAV client smtp.rs SMTP (lettre) / sendmail ical.rs iCalendar/vCard parser and serializer tz.rs EAS <-> IANA time zones, dates mime.rs MIME building (SmartReply/Forward) mod.rs session: folder hierarchy, fingerprint listings, operations autodiscover.rs admin.rs panel API, sessions, CSRF, SSE, Prometheus web/ panel (dependency-free HTML/CSS/JS, embedded in the binary) ``` ### Synchronisation The device state of a folder is a map `item id -> fingerprint` (IMAP: flags; DAV: ETag) stored under the key `{uuid}N`. Sync compares the backend listing with the state and sends the differences (deletes -> changes -> adds, newest mail first) within `WindowSize`. The new state is stored as `N+1`; the previous one is kept to handle lost responses (the device replays the old key). Client adds are idempotent thanks to the `ClientId` map. Ping keeps a folder signature (IMAP `STATUS UIDNEXT/MESSAGES/UNSEEN/HIGHESTMODSEQ`, DAV CTag); a full listing comparison only happens when the signature changes. Collection ids that a device uses but that do not exist (Z-Push leftovers) are remembered per device and sent as FolderHierarchy Delete on the next FolderSync. ## Development environment ```bash sudo apt install dovecot-imapd # any dovecot >= 2.3 pip install radicale aiosmtpd ./dev.sh backends # Dovecot :11143, Radicale :15232, SMTP sink :12525 ./dev.sh seed && ./dev.sh # ActivePush :8081 / panel :9091 ./dev.sh e2e # test of every command against live backends ``` Test accounts: `jan`, `anna`, `biuro`, `kasia` `@test.local`, password `Test-Password-123`. `./dev.sh lint` - rustfmt, clippy `-D warnings`, JS syntax check and WBXML tag name check (`scripts/check-wbxml-names.py`). `./dev.sh test` - unit tests (WBXML, IMAP tokenizer, UTF-7, calendar/contact/ task conversions, time zones, query parser, CIDR, MIME). Protocol debugging: add the login to **ActiveSync -> Logging -> Debug users**; decoded WBXML of requests and responses goes to the log. Simulating devices: `active-push probe --device-id F2LX8KQ1N72J --device-type iPhone --model "iPhone 15 Pro" ...`. ## Documentation screenshots `tools/dev/screenshots.js` (Playwright) regenerates `docs/images/` from a running dev instance: ```bash node tools/dev/screenshots.js http://127.0.0.1:9091 admin 'password' ```