Hides the SSL/TLS toggle in add/edit account screens when the host is
not localhost; enforces SSL in connectImap/connectSmtp for non-localhost
hosts so plaintext can never be configured accidentally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The "Email filters" screen was failing with WRONG_VERSION_NUMBER because the
ManageSieve client was opening implicit-TLS sockets on port 4190, while RFC
5804 servers (Stalwart, Dovecot, Cyrus) listen plaintext on 4190 and expect
STARTTLS. ManageSieveClient.connect now opens plaintext, reads the capability
greeting, sends STARTTLS, hands the socket to SecureSocket.secure(), and
re-reads capabilities on the encrypted stream.
The same WRONG_VERSION_NUMBER error can hit IMAP/SMTP when the SSL toggle and
the chosen port disagree (e.g. SSL=on with SMTP port 587). New helper
lib/data/imap/tls_error.dart translates that BoringSSL error into a
TlsModeMismatchException naming the host/port and suggesting which port goes
with which TLS mode. connectImap, connectSmtp, and the ManageSieve TLS
upgrade all funnel through rethrowAsTlsHint so the same readable message
reaches the UI regardless of which protocol failed.
ConnectionTestService previously only verified IMAP/JMAP, so SMTP and
ManageSieve misconfig silently passed the "Try connection" button on the
edit-account screen and only surfaced when the user later tried to send
mail or open Email filters. After IMAP succeeds, the service now also
verifies SMTP (always — sending mail requires it) and ManageSieve (only
when manageSieveHost is explicitly set, since the section is collapsed by
default). Failures are prefixed with "SMTP:" or "ManageSieve:" so the
user can tell which leg of the connection is broken.
connectionTestServiceProvider now also watches smtpConnectProvider so the
E2E integration tests' plaintext SMTP override applies to the connection
check as well.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a minimal ManageSieve client so the existing "Email filters" UI
works for IMAP accounts, not just JMAP. SieveRepository becomes a
dispatcher that routes to JMAP or ManageSieve based on account.type.
Account model + DB schema v15 grow manageSieveHost/Port/Ssl fields
(default 4190 / TLS, host falls back to imapHost when blank). The Add
and Edit account screens expose them inside a collapsed ExpansionTile
to keep the form short for users who accept defaults.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stalwart 0.14.x does not increment HIGHESTMODSEQ when new mail arrives
via SMTP delivery, so the incremental sync's CONDSTORE fast-path saw
serverModSeq == storedModSeq and returned early — silently skipping
UID SEARCH and missing any newly received messages.
Fix: remove the early-return fast-path. Incremental sync now always
runs UID SEARCH UID ${lastUid+1}:* to discover new messages. CONDSTORE
is still used for the flag-refresh gate (only runs when modseq changed),
which is its correct, narrower role.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Added lint rule to analysis_options.yaml and ran dart fix --apply to convert
125 relative imports in 33 files to package:sharedinbox/... style.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
isLogEnabled now depends solely on the verbose zone flag, not kDebugMode.
Logs only flow to the sync log when account.verbose is true; stdout stays
silent in all builds.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When account.verbose is true, raw IMAP/JMAP protocol traffic is captured
via a Zone, redacted of credentials (LOGIN password, AUTHENTICATE tokens),
and stored in the sync log entry for display in the sync log screen.
- DB schema v13: adds verbose column to accounts, protocolLog to sync_logs
- IMAP: Zone print-capture feeds ImapClient isLogEnabled output
- JMAP: JmapClient.call() writes request/response bodies to zone buffer
- Sync log screen: shows a monospace "Protocol log" block when present
- Edit account screen: adds verbose toggle with warning subtitle
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sets isLogEnabled=kDebugMode on ImapClient so raw IMAP traffic appears
in the debug console without touching release builds.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: flutter test ran all 3 integration test files in parallel
against the same Stalwart instance. Concurrent SMTP/IMAP from
email_repository_imap_test and concurrent_sync_test caused SMTP rate
limiting (4th send hung for ~27s) and flushPendingChanges race failures.
Fixes:
- stalwart-dev/test.sh: add --concurrency=1 so test files run serially
- concurrent_sync_test: reduce timeout 2 min → 30 s (tests now pass in ~2s)
- imap_client_factory + test helpers: set defaultResponseTimeout=20s on
ImapClient so individual IMAP commands never block indefinitely
- jmap_client: reduce HTTP call timeout 30 s → 10 s (local server; keeps
stacked-timeout total well below any reasonable per-test limit)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move ImapConnectFn typedef to imap_client_factory so it can be shared.
Inject it into AccountSyncManager/_AccountSync so tests can substitute a
no-op instead of hitting a real IMAP server.
_AccountSync._sync() now iterates all mailboxes from the repository after
syncMailboxes, mirroring the JMAP loop that was already in place.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
IMAP/SMTP encryption:
- connectImap throws if account.imapSsl is false
- connectSmtp removes STARTTLS plaintext fallback; startTls failure is fatal
- Remove IMAP SSL/TLS toggle from add/edit account screens (always SSL)
- UI shows "IMAP (SSL/TLS)" section label to communicate the requirement
Pre-commit speed:
- Add check-fast task (analyze + unit + widget, no build-linux, no integration)
- pre-commit hook now runs task check-fast instead of task check
- task check remains the full suite for manual/CI use
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Account model gains `username` field (default empty → falls back to email then local-part)
- ConnectionTestService returns the effective username that succeeded; tries email then local-part when blank
- JMAP connection probe uses Basic-auth GET to /.well-known/jmap (401/403 = auth failure)
- IMAP/SMTP factory passes explicit username parameter
- Add/edit account screens show username field and "Try connection" button
- EditAccountScreen reuses stored password when no new password is entered
- Unit tests for ConnectionTestServiceImpl (IMAP + JMAP paths, fallback logic)
- Fix unit test lambda signatures for updated ImapConnectFn/SmtpConnectFn
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- enough_mail: use uidFetchMessage/uidMarkSeen/uidMarkFlagged/uidMove/
uidMarkDeleted/uidExpunge, remove non-existent isUidSequence param,
fix SmtpClient construction and use quit() not disconnect()
- Drift: add @DataClassName('MailboxRow') to avoid ugly 'Mailboxe',
alias core model imports to resolve type name conflicts
- EmailsCompanion.insert: uid/receivedAt are required, not Value<T>
- Lint: remove unrecognised rules (prefer_const_collections,
avoid_returning_null_for_future), add missing mounted guards after await
- Tests: fix html_utils expectations to match trim() behaviour,
add explicit Map casts in email_model_test for avoid_dynamic_calls
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
IMAP/SMTP email client with offline-first architecture:
sync engine writes to Drift (SQLite), UI reads reactively
from the local DB. enough_mail vendored under packages/.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>