Matthew Meszaros
fb453921db
feat: keep a failed message-map rollback pending on the mailbox and retry it at the start of every IMAP, Gmail and Graph sync pass, skipping the pass until it succeeds so an unpublished arrival is never read as known
2026-09-22 04:59:27 -07:00
Matthew Meszaros
9009c6ec24
feat: build inbound bounce and complaint reports before NEW_EMAIL but publish them only after it succeeds, so a message re-offered after a failed arrival publish does not apply its deliverability report twice
2026-09-22 04:49:26 -07:00
Matthew Meszaros
4a6f0c17c7
feat: advance each IMAP folder's sync cursor to the SELECT view its search ran against instead of the earlier LIST-STATUS, so Sent copies appended between the two are no longer skipped, and drop the message-map entry when NEW_EMAIL fails to publish so an unpublished message is re-offered instead of read as known ( #645 )
2026-09-22 04:39:18 -07:00
Matthew Meszaros
3ea6118a27
feat: redeliver a warmup retention delete when the mailbox is not loaded on the worker, drop the stored body when the IMAP message is already gone on a redelivery while returning a search failure rather than treating it as absence, and encode the accepted warmup_retention_days range (0, or 3 to 3650) in both OpenAPI schemas
2026-09-21 01:21:28 -07:00
Matthew Meszaros
0a5e7947b4
feat: return a failed warmup retention delete from the worker so the bus redelivers it up to five times, re-key a Graph message in the map on every move so the sender copy's body can be dropped, never expunge a whole IMAP folder for one message (UID EXPUNGE, else MOVE to Trash, else refuse), build the two retention indexes concurrently in their own migrations 000193 and 000194, keep the dashboard stepper off 1 and 2 days, and describe retention as applying wherever the placement files warmup mail
2026-09-21 01:11:22 -07:00
Matthew Meszaros
1513419a2a
feat: delete warmup mail from each mailbox once past a per-mailbox retention window (email_accounts.warmup_retention_days, else retention.warmup_mail_days, default 30) via a consumer sweep that retires the receipt and sender copy and a worker delete action that trashes on Gmail, deletes on Graph, expunges on IMAP and drops the stored body, prune per-message warmup records after retention.warmup_event_days, and count a warmup deletion as tampering only within 24 hours of arrival and never for a retired message, judging Gmail's Trash label on the same rule
2026-09-21 00:52:02 -07:00
Matthew Meszaros
48ecca2400
feat: follow the provider's read state in the unibox by storing seen from the \Seen flag on every IMAP, Gmail and Graph arrival, carrying read-state changes onto unibox_emails.seen in the FLAGS_ADD/FLAGS_REMOVE and UPDATE_EMAIL handlers, and backfilling existing rows from their flags in 000190
2026-09-20 06:54:29 -07:00
Matthew Meszaros
392bcc0478
feat: run the mailbox credential check off the worker's bus loop so it no longer waits behind queued sends and mailbox loads until the backend's fourteen-second wait expires, have the worker always answer with an error verdict when it cannot unseal the credentials so an untested mailbox is a server error rather than a mail-server timeout, name the leg that stayed silent and say whether the other one signed in with a 587 hint when 465 hangs, word the no-reply case as the worker not reporting back, dial both probes from WORKER_BIND_IP like the send and sync clients, and connect Gmail app-password mailboxes over 587 with STARTTLS because many hosts block outbound 465
2026-09-20 01:54:12 -07:00
Matthew Meszaros
abe6157d37
feat: tighten the mailbox connect probes after a self-review, turning every socket error and not only a failed dial into closed words so a net.OpError naming the worker's bound address never reaches a customer, keeping a server reply's reason when the deadline passes while it is read instead of reporting a refused password as a timeout, connecting TCP before the TLS handshake on implicit-TLS SMTP so a port that answers and then fails the handshake reads as a TLS problem on both legs, sending EHLO explicitly because net/smtp's Extension swallows a failed greeting and reported a server that hung up as a mailbox with nothing to verify, and widening the backend's wait to fourteen seconds so the worker's seven-second budget plus the bus fits inside it
2026-09-20 00:21:31 -07:00
Matthew Meszaros
8d3fa5fe01
feat: address the review on the mailbox connect verdict by describing a failed dial in closed words so a Go dial error naming the worker's own bound address never reaches a customer, counting only SMTP 534 and 535 and a tagged IMAP NO as a refused sign-in while a BAD, a 504 or a 530 is reported as the conversation failing, classifying an IMAP LOGIN refusal before the LOGOUT goes out and not waiting for its answer, bounding the worker's unseal plus probes to seven seconds so the verdict always lands inside the backend's nine-second wait, and saying in the docs that the message quotes the server only when it answered and that a different password adds no auth mechanism
2026-09-20 00:08:16 -07:00
Matthew Meszaros
c8162966a3
feat: tell a person connecting a mailbox what the mail server actually said instead of "invalid credentials" for everything, by having the worker's SMTP and IMAP probes classify a refused sign-in, an unreachable host, a failed TLS handshake, a retry-later reply and a timeout and publish that verdict as JSON ahead of the legacy digit, mapping it on the backend to mailbox_auth_refused, mailbox_unreachable, mailbox_tls_failed and mailbox_server_declined with the server's reply and a Gmail app-password hint in the message, starting the probe budget after the credentials are unsealed and bounding the SMTP conversation so a silent server no longer parks the worker, and normalizing passwords on every connect path so a Google app password pasted with its spaces works from the form, the CSV import, the API and the re-authorize dialog alike
2026-09-19 23:53:40 -07:00
Matthew Meszaros
acecd62c88
feat: scope mailbox disconnect and warmup lifecycle to the workspace rather than the member who connected the mailbox so an admin can act on every mailbox the list already shows them, evict a mailbox whose row is gone from every live worker when its provider errors arrive so a deleted mailbox stops calling the provider once a sync interval forever, subscribe before publishing the credential-validation job and classify a socket deadline as the retryable timeout it is, give the worker's validation reply its own budget so a slow mail host no longer loses a finished verdict, guard every global key handler against a keydown carrying no key, drop exceptions whose whole message is an object's default toString, make the Postgres pool size configurable, and record the CASA and security invariants in AGENTS.md
2026-09-19 12:49:46 +02:00
Matthew Meszaros
68c3676717
feat: keep warmup out of the customer's own mailbox and off their deliverability record: Gmail foldering now removes INBOX and SENT instead of only labelling, sent copies and reply-backs are filed in both directions, filing is configurable per mailbox (folder/inbox/archive via warmup_placement + warmup_folder, migration 000177), IMAP relocates a moved message by Message-ID so read/important stop no-opping, and a warmup send's bounce notice no longer lands in the unibox or suppresses a pool partner
2026-09-17 20:46:03 -07:00
Matthew Meszaros
a900f1e04c
feat: make worker moves atomic and preserve safe concentration and health state
2026-09-17 07:45:23 -07:00
Matthew Meszaros
bab9f86727
feat: correct worker capacity, mailbox distribution, observed IPv4, fleet pagination, and premium pool promotion
2026-09-17 04:15:05 -07:00
Matthew Meszaros
817599d0eb
feat: keep provider mail throttles retryable without deactivating mailboxes or flooding error tracking
2026-09-16 17:42:12 +02:00
Tung Lam
dd4234980b
fix: reconcile expunged IMAP drafts so a Gmail autosave replacement stops leaving duplicate copies in a thread, by diffing each drafts folder's live UID set against the rows the backend holds for that UIDVALIDITY generation and removing the ones the server no longer reports, only in drafts and only when the selected generation still matches the listing (issue #516 )
2026-09-15 00:00:20 -07:00
Matthew Meszaros
619eb2729a
fix: read a mailbox's Gmail send-as addresses from the worker holding it rather than from the backend, which was decrypting a mailbox credential in the control plane and showing Google a second client address for a mailbox whose mail moves through a worker, by round-tripping a new MAILBOX_IDENTITY command answered on the process channel like a credential validation, leaving the OAuth handshake as the one place the control plane still calls the provider ( #522 )
2026-09-14 21:28:07 -07:00
Matthew Meszaros
1ca3bd19b3
feat: carry a unibox read or unread change out to the mailbox itself through a new MESSAGE_SEEN worker command, so a conversation read in Warmbly stops showing bold in Gmail, Outlook and IMAP, relaying the state the row holds rather than the one the request asked for, only for messages that actually changed, dispatched detached from the request and never retried ( #515 )
2026-09-14 21:20:42 -07:00
Matthew Meszaros
92e298b6ec
fix: clear every open issue in error tracking by fixing the bugs behind them rather than the reports: a document-level mouseleave handing RippleProvider the document itself, whose classList is undefined; the admin panel posting /getaway without the /v1 its baseURL omits, so its realtime socket 404d on every page; Gmail throttles classified from the 403 status alone and told to re-authorize instead of back off; consumer flag and folder events retrying forever on a message the unibox never stored; a lost token-refresh race answered 500 instead of the documented 401; a nil email_accounts slice crashing the admin user page; Turnstile mounted with an empty sitekey; conditional passkey autofill run after the user navigated away; a boot log filed as an issue; and one publish failure per message on a topic the broker refuses ( #519 )
2026-09-14 21:06:14 -07:00
Matthew Meszaros
8d790ede6c
feat: send from any address Google has verified a Gmail mailbox to send as and import the signature its owner already wrote in Gmail, reading both through gmail.settings.basic at connect and on demand via GET/POST /emails/:id/identity, validating the choice against the provider's own list in the service and again inside the UPDATE, clearing it when the provider stops verifying it, and never applying it to warmup ( #514 )
2026-09-14 10:13:36 -07:00
Matthew Meszaros
c28f915648
feat: erase everything a disconnected mailbox leaves behind, revoking its OAuth grant at Google and deleting its stored message bodies through a durable retried queue, cascade the nine mailbox foreign keys that had none so warmup receipts, tampering events and provider message maps stop outliving the mailbox, clear thread labels and snoozes on conversations the delete emptied, make workspace deletion possible at all by cascading the four organization foreign keys with no delete action, and put Disconnect in the mailbox row menu and a Settings danger zone since it was only reachable from the selection bar ( #506 )
2026-09-14 07:55:01 -07:00
Matthew Meszaros
6b6efca865
fix: campaign follow-ups opened a new conversation instead of replying in the contact's thread, so carry In-Reply-To/References and the Gmail threadId from the previous send, give every step a reply-in-thread switch, and let a threading step inherit the conversation's subject (issue #472 ) ( #489 )
2026-09-13 20:51:41 -07:00
Matthew Meszaros
017f4cf60f
feat: plans an operator can grant, visible in the admin panel ( #467 )
...
* feat: add operator-granted plans so a workspace can be paid without Stripe, surfaced in the admin panel as a badge, a filter and a card carrying who granted it and why, because the only alternative was writing a fake stripe subscription id into the database
* fix: hold a granted plan beside the paid one rather than over it so a Stripe workspace returns to the plan it pays for when the grant ends, route entitlement lookups through EffectivePlanID, separate a repository failure from an unknown plan, end a grant at local end of day, and drop an index that served no query
2026-09-12 09:45:31 -07:00
Matthew Meszaros
47defafa09
feat: fix the six self-host defects reported in issue #439 ( #456 )
...
* feat: fix the six defects reported in issue #439 by mapping the IMAP UNAVAILABLE, INUSE and NONEXISTENT response codes to retry-level errors instead of a critical reconnect prompt, synthesising a stable no-msgid key so one message with no Message-ID header can no longer 400 the internal map endpoint and wedge every later sync pass with its cursors held, adding mailhtml.FromText and HasContent so an API or agent-created step with a plain body stops shipping the composer's empty div placeholder as its text/html part (derived on create and plain-only update, exposed as body_html on update_campaign_step, dropped at send and preview time, and refused at campaign start with empty_step_body), honouring sender_strategy='explicit' in ResolveCampaignSenderPool and ValidateCampaignReady so an emptied explicit pool parks the campaign instead of widening it to every mailbox in the workspace, making the paused_no_accounts auto-pause loud with an error log line, an error-level activity-feed entry and an org-scoped CAMPAIGN_PAUSED realtime pulse, gating the admin sign-in's Turnstile widget on GET /v1/auth/config so a self-host with CAPTCHA_PROVIDER=none is not locked out, and parsing NATS_URL down to its host:port so a credentialed bus URL no longer reports NATS down
* feat: act on the self-review of the issue #439 fixes by dropping the campaign wizard's own escapeHtml body_html builder, which entity-escaped the quotes in a conditional and made the template fail to parse at send time, and letting the backend's FromText render that part instead so wizard-written steps also get their bare URLs linked for click tracking, correcting the docs and openapi description that claimed an explicit sender pool never falls back when it still unions its tags as migration 000013 designed, extracting the duplicated blank-HTML-part guard into dropBlankHTMLPart shared by the send path and the preview, and recording why the no-msgid key keeps the folder name despite a RENAME changing it
* feat: address the CodeRabbit review on the issue #439 fixes by holding the admin sign-in's Turnstile widget unmounted until /v1/auth/config resolves so an instance with no route to Cloudflare cannot raise a widget error on a screen nobody submitted, failing StartCampaign closed when the sequence read errors rather than skipping both the malformed-template and empty-body refusals, giving TCPCheck the default port its protocol assumes so a portless NATS_URL is no longer reported down, leaving a URL that carries a merge field unanchored because the send path renders bodies with text/template and a quoted contact value would break out of the href, and correcting the sequences guide and the Campaign and CampaignUpdate openapi descriptions that named the wrong tag field
2026-09-12 03:13:38 -07:00
Matthew Meszaros
2998f8a8c6
Merge remote-tracking branch 'origin/main' into feat/worker-capacity-soft-target
2026-09-10 06:26:47 -07:00
Matthew Meszaros
fa2b5330d7
feat: drop the auth-pressure placement term because worker_capacity_view aggregates auth_errors (per-mailbox credential failures) and not rate_limit_errors (the 454/421 per-IP throttles it claimed to measure), measure projected utilization against an age-free Capacity.Target so a freshly joined node can relieve a full fleet instead of scoring as 200% loaded after one mailbox, bound the isolated-egress override with an explicit OverTarget check now that Eligible no longer caps it, and cap rotation moves per destination since a tick scores every mailbox against one frozen materialized-view snapshot
2026-09-10 05:51:17 -07:00
Matthew Meszaros
7fa4fdfbc4
feat: make worker capacity a placement target rather than a hard gate, so Eligible refuses only on health and an over-target worker costs enough score to lose to anything with room instead of returning nil and dropping assignment into selectFallback, score projected utilization including the incoming mailbox's own weight, and penalise the 454/421 auth pressure the capacity view already collected and threw away
2026-09-10 05:38:17 -07:00
Matthew Meszaros
47ba13083e
feat: make a split deployment work end to end by fixing the three defects that made an off-host node impossible to configure (nodeEnvKeys shipped S3_BUCKET and KMS_KEY_ID, which nothing reads, so an AWS-backed node silently used the default bucket and key alias; a joined consumer never received PRIMARY_DB and died at boot; and node.env was rewritten on every join with no file an operator could add to), then removing the need for cloud credentials on a node at all with brokered KMS and blob providers that renderNodeEnv hands out automatically, plus deploy/split-cloud, scripts/aws-bootstrap.sh, two fleet instance checks and the docs
2026-09-10 13:58:59 +02:00
Matthew Meszaros
6ebf9cfdcf
Merge remote-tracking branch 'origin/main' into fix/self-hosted-unsubscribe-domain
2026-09-09 08:24:04 -07:00
Matthew Meszaros
7d58b874b8
feat: keep every recipient-facing and self-host-facing address on the deployment's own domain: mint unsubscribe links on a workspace's verified tracking domain (served by the tracking service, proxied to the backend that owns the pages), attach RFC 8058 one-click only over https, resolve all branding through config.Brand() gated on SelfHosted() so a self-host's email footer, sign-in links, stats card, API example and public form badge name nobody else, drop the app.warmbly.com fallback from AppBaseURL, blank TRACKING_DOMAIN and FORMS_DOMAIN on core-only installs, and have install.sh offer to configure a fresh interactive install instead of silently defaulting to localhost
2026-09-09 06:34:43 -07:00
Matthew Meszaros
bfdbd77b04
feat: fix the eight defects the second review pass found, including three where the previous fix did not land: the bind-mounted state dir was root-owned so the node running as uid 1000 still could not write its update target, elevating the reserved-worker eviction did nothing because the rotation loop bailed on target-equals-current before the urgency was consulted, and the warmup-pool assertion was vacuous which hid that warmupPoolFor checked the subscription repo before the billing provider and answered free on a self-host install
2026-09-09 05:32:30 -07:00
Matthew Meszaros
8b83062eff
feat: fix ten defects the review found, the worst being that every seeder still wrote the worker columns migration 000141 moved to fleet_nodes so make dev could not seed, that the generated systemd unit used a command substitution systemd never expands so a joined machine restart-looped while the script reported success, that the state directory was not bind-mounted so the node wrote its update target inside the container where the host timer never saw it, and that placement stopped assigning warmup pool membership so paid mailboxes silently warmed in the free pool
2026-09-09 05:21:17 -07:00
Matthew Meszaros
de60bc3784
feat: let an isolated-egress reservation move a mailbox without clearing the score gain, because the reserved worker scores lower than the incumbent by construction (the incumbent holds the stickiness bonus) so the rotation loop refused the move on every tick and the organization never converged onto the worker it pays for
2026-09-09 05:02:53 -07:00
Matthew Meszaros
435dbb522f
feat: replace the worker tier/type/risk-pool/egress categories with a scored placement model and make the fleet pull-based, so a machine joins with one command, workers and consumers share one node registry with usage and liveness, nodes self-update to the version the control plane resolves, and the Hetzner provisioning, worker profiles and SSH orchestrator are removed
2026-09-09 04:54:01 -07:00
Ralf Klein and Claude Opus 5
3cef4e8268
fix: thread an IMAP message on In-Reply-To only, never on the sender's Reply-To
...
imapStore fell back to Reply-To as the thread parent whenever a message
carried no In-Reply-To. Reply-To is an address header -- send replies to
this mailbox -- not a message identifier, so the thread key became a
person's address and every message that sender ever sent collapsed into
one strand.
Measured on a production instance with 38 IMAP mailboxes: 484 of 1431
stored messages sat in 62 threads. One reporter's 76 unrelated DMARC
aggregate reports arrived as a single 76-message conversation, and one
mailbox's sequence tests, compose tests and live outreach merged into
another. The remaining 947 messages, which did carry In-Reply-To, were
threaded correctly.
Sequence safety was never affected: stop_on_reply matches In-Reply-To
against the task rather than the thread. The damage is to the unibox,
where replies are triaged.
A message that answers nothing now has no parent and roots its thread on
its own Message-ID, which is what the existing else branch already does.
The test fails if the fallback is reintroduced.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com >
2026-09-08 12:20:52 +02:00
Matthew Meszaros
8b757985f3
feat: address the review on the folder identity change by retiring a deleted folder's backfill floor with it, since a name is reusable and an inherited floor silently skips the next folder's history, by claiming a rename only when a UIDVALIDITY has exactly one missing folder and one new one, because two missing folders and one arrival cannot say which was renamed and guessing moves the wrong folder's mail, by deduplicating names before the folder cap rather than after so a name listed twice cannot spend a real folder's slot, by deleting on a legacy UIDVALIDITY-only event only when that number still names exactly one folder, and by moving the folder row and its mail in one transaction so a refused rename cannot leave the messages in a folder nothing renamed
2026-09-07 08:49:23 -07:00
Matthew Meszaros
769a05aa90
feat: identify an IMAP folder by its name rather than by its UIDVALIDITY, which RFC 3501 never promised was unique across folders, so a mailbox on a server that stamps that number with the folder's creation time no longer loses the entire sync of every folder in a tree created in the same second, with the folder row keyed on (email_id, mailbox), each stored message stamped with its folder's name alongside the UIDVALIDITY generation its uid belongs to, a rename followed as a move that carries the mail and the cursor instead of orphaning both, and a changed UIDVALIDITY treated as what it is, the cursor going void
2026-09-07 08:21:17 -07:00
Matthew Meszaros
1239f1d191
Merge main into the Sentry branch, routing the new smtp reporting through the errs wrapper instead of the SDK import main added
2026-09-07 05:00:43 -07:00
Matthew Meszaros
4582896f54
merge
2026-09-07 04:43:45 -07:00
Matthew Meszaros
be16f77f5d
Merge main into the Sentry branch
2026-09-07 04:25:41 -07:00
Matthew Meszaros
1af5ac1ea6
feat: let a fixed mailbox problem stop showing as a permanent error (issues #362 , #363 ): resolve a mailbox's connection errors on the first sync pass that reaches the server again, because nothing but a credential reconnect ever resolved an error row, so a five-minute outage left a red needs-attention on the mailbox for good and held its health at warning; carry what the folder listing had to skip as sync state shown in the drawer's Sync card instead of raising an error row nobody can withdraw, so the note disappears once the mailbox is back under the folder limit or the duplicate folder id is renamed; and keep only a fingerprint per message in the IMAP flag scan rather than the whole flag set and Message-ID, which held 74 MB per mailbox at the window and folder limits against 14 MB for the digests, on a worker whose base capacity is 16 mailboxes
2026-09-07 04:24:09 -07:00
Matthew Meszaros
211650af8d
feat: send through every kind of SMTP server (issues #359-#361): negotiate the sign-in method from what the server advertises, preferring CRAM-MD5 then LOGIN then PLAIN, because sending AUTH PLAIN blind was refused by every server that offers only LOGIN, which is Microsoft 365 relays and most appliance relays, and that refusal was reported to the mailbox's owner as a wrong password and deactivated the account; put a deadline on the whole SMTP conversation so a peer that stops answering without closing the connection can no longer park a send goroutine forever, which only the dial was protected against; classify a refusal by its reply code so a permanent 5xx on the sender, the recipient or the message is reported as the rejection it is and not retried four times as though the server were offline, while a 4xx still retries; announce the sender's own domain in EHLO rather than net/smtp's localhost, which relays read as a spam signal; and share one AUTH LOGIN implementation with the notification mailer instead of keeping two copies of the code that handles credentials
2026-09-07 04:19:42 -07:00
Matthew Meszaros
51dedc90ee
feat: put every runtime behind one optional error-reporting story: a single internal/observability/errs wrapper that is now the only package importing sentry-go, InitSentry for cmd/forms, release and environment tags on every service from the existing build stamp, optional Sentry in the admin panel and the public forms app, the sentry crate in the Rust tracking service, release tagging in realtime, CI source-map upload that only runs when a Sentry token is configured, and docs covering the DSN for each service
2026-09-07 03:51:06 -07:00
Matthew Meszaros
875cea477f
feat: give a duplicate IMAP folder id its own error code and guidance instead of reusing the folder-limit one, because the two problems have different fixes: getting under the folder cap versus renaming the folder the mail server gave a duplicate UIDVALIDITY, and the drawer was telling a user to delete folders when neither the cap nor deleting anything was involved
2026-09-07 03:42:57 -07:00
Matthew Meszaros
883178718e
feat: address the CodeRabbit review on the IMAP compatibility PR by guarding the NIL hierarchy delimiter so a server that reports none is not given a NUL separator that hides the leaf fallback and loses the Sent folder, serializing the idle connection's deadline state under a mutex so a warmup action finishing cannot clear the deadline out from under a sync fetch still in flight on the same session, and threading the delimiter the server itself reports through the folder classification so a folder under a separator other than a dot or a slash is recognized instead of filing as inbox
2026-09-07 03:27:50 -07:00
Matthew Meszaros
90f5e8c625
feat: follow only one folder per UIDVALIDITY on IMAP, because the folder row is keyed on that id while RFC 3501 only promises UIDs are stable within a folder, so a server that derives it from the creation time gives a folder tree made in one second a single shared id and the two folders would advance each other's cursor and delete each other's row; the inbox and special folders win the collision, the dropped folder is reported in the mailbox drawer and named in the worker log, and the folder classification, ranking and localized-name matching gain direct tests
2026-09-07 03:20:46 -07:00
Matthew Meszaros
f2c35cf872
feat: sync every kind of IMAP mailbox (issues #345-#349): connect servers without CONDSTORE by following UIDNEXT per folder and mirroring read state with a periodic flag scan, so Outlook.com, Microsoft 365 over IMAP and Yahoo work instead of failing at load; fall back to a STATUS per folder when the server has no LIST-STATUS, which silently made an account look empty; bound the wait between IMAP responses so a peer that vanishes without a FIN cannot park a command forever; keep the inbox and special folders when a mailbox has more folders than the cap (raised to 100) and relay the overflow as a warning instead of failing the mailbox silently; recognize localized folder names and the server's own hierarchy delimiter so a Sent folder called Gesendete Elemente is not filed as inbox; and back off on a widening interval while a mail server is unreachable so one outage is one warning rather than one a minute
2026-09-07 03:12:22 -07:00
SUMAN JANA
5eaf4481d5
Merge branch 'fix/imap-dead-session-reconnect' of https://github.com/rocker1166/warmbly into fix/imap-dead-session-reconnect
2026-09-07 07:15:49 +00:00
SUMAN JANA
b9fa144d1c
fix(worker): guard the IMAP client across reconnects; Gmail-only name fallback
...
Address review:
- The client field was read by commands while ensureConnected could swap
it. A lifecycle RWMutex now holds the write lock through dial, auth and
assignment, and every command holds the read lock for its duration.
Lock order is mu before lifecycle; sentMailbox resolves under mu before
AppendToSent takes the read lock.
- The virtual-folder name fallback applied to any server, so a plain IMAP
account with a real folder called "Important" or "Starred" would have
been dropped from sync. It now applies only inside Gmail's own
"[Gmail]/" and "[Google Mail]/" namespace; regression cases added.
2026-09-07 07:15:20 +00:00