Commit Graph
144 Commits
Author SHA1 Message Date
Matthew Meszaros e920b61371 feat: clear a reported warmup send failure only on a confirmed delivery, word the not-loaded send failure as a possible cause, leave placement seeds out of the adopted-mailbox warmup repair, and describe send_failure as a failure reason in the API schema 2026-10-01 03:20:41 -07:00
Matthew Meszaros a844c506a0 feat: make Warmbly Cloud warmup start and explain itself for linked mailboxes: apply the instance's warmup settings, start warmup on adopted mailboxes, read the warmup day mask Monday-first, report the last refused warmup send and the partner cap to the instance and dashboard, hand changed SMTP credentials to the cloud, refuse cleartext mailboxes at enrollment, and show cloud warmup with the warming animation and a clear Cloud marker 2026-10-01 03:01:36 -07:00
Matthew Meszaros 1f0c6ca6d6 feat: send each mailbox's reply-to as a Reply-To header on campaign, unibox, test and placement mail (never warmup), record it on every campaign send attempt (tasks.reply_to, migration 000236) and credit a reply landing in that shared reply inbox to the campaign and its sending mailbox across reply attribution, the sync priority lane and copied-contact replies, show the sending mailbox on thread messages, recent activity and the reply webhook, start unibox and inbox-agent replies from the mailbox that emailed the contact, flag an untracked reply-to in mailbox settings, let the sandbox simulator answer Reply-To, and document the shared reply inbox (#756) 2026-09-30 05:33:34 -07:00
Matthew Meszaros b2d6d100dd feat: relay unibox Archive, Delete and Move to inbox to the mailbox itself through a new MESSAGE_FOLDER worker command (Gmail label batchModify, Outlook well-known folder moves with the message map re-keyed around each Graph id change, IMAP MOVE after locating each message by Message-ID), answered by relayed UPDATE_FOLDER events that write only provider_folder and the moved handles, relaying every row a filing moved so an Undo right after Archive still reaches the mailbox, with a per-mailbox Mirror Archive and Delete switch (email_accounts.relay_folder_moves, migration 000235, on by default), warmup receipts, drafts and non-Gmail sent copies left in place, and the unibox, mailboxes, API, events and OpenAPI docs updated 2026-09-30 04:41:03 -07:00
Matthew Meszaros d162f0aaf6 Merge pull request #748 from warmbly/fix/gmail-archive-unibox-inbox-sync
feat: file Gmail mail where Gmail moves it (archive, delete, spam, back to inbox) via an UPDATE_FOLDER event resolved against provider_folder, repair rows already stranded in the unibox Inbox with a six-hourly Gmail folder reconciliation, and close the open unibox conversation on a scope change with a close button in the reader header
2026-09-29 17:57:41 +00:00
Matthew Meszaros 974c68cd92 feat: bound the Gmail folder reconciliation's inbox listing by the oldest stored row in any reconciled folder, so an archived, spam or trash row older than every inbox row that Gmail moved back to the inbox is still found 2026-09-29 10:51:54 -07:00
Matthew Meszaros 3290360333 feat: keep the Gmail folder reconciliation going past a message Gmail refuses, list the inbox by label, remove rows Gmail no longer has, retry a failed pass after 15 minutes, report each Gmail move once per tick, answer the internal listing with a fixed error, and keep the conversation open when widening to All mail 2026-09-29 10:40:34 -07:00
Matthew Meszaros 0ed98a8c55 feat: copy up to two colleagues on every email a campaign sends one lead (campaign_lead_cc, migrations 000230-000231) with a drawer CC editor that suggests same-company contacts, hold a copied contact's own lead so nobody gets two threads, count a copy's reply as the lead's, opt out every copy on a link unsubscribe, drop a bounced or refused copy without bouncing the lead, skip suppressed campaign CC and BCC addresses, and document the endpoints, CLI, skills and OpenAPI 2026-09-29 09:53:27 -07:00
Matthew Meszaros e2f319cfd8 feat: file Gmail mail where Gmail moves it (archive, delete, spam, back to inbox) via an UPDATE_FOLDER event resolved against provider_folder, repair rows already stranded in the unibox Inbox with a six-hourly Gmail folder reconciliation, and close the open unibox conversation on a scope change with a close button in the reader header 2026-09-29 09:43:13 -07:00
Matthew Meszaros ad4104c57a feat: never add a warmup strike from a recheck (confirm it, or withdraw it when the message is still in the mailbox or retention removed it since), revise a tampering hold before its strike is deleted so a retry completes it, compare-and-swap the revision on the hold's term, scope the ledger revision to the whole address, keep tampering events 37 days so a live hold's strikes survive pruning, answer an inconclusive search as unknown so IMAP rechecks stop, and return an IMAP transport failure as an error 2026-09-29 05:12:36 -07:00
Matthew Meszaros e5eb3b0ff1 feat: re-decide a withdrawn warmup tampering hold on the strikes left in the seven days before it was imposed (on the pool row, or the reputation ledger for a mailbox out of every pool), stamp an old strike verified only when a worker answers its search and ask again after six hours, retry a removal check for a mailbox still loading, redeliver a failed strike, search IMAP folders in one session hold, and share the Message-ID search helpers 2026-09-29 04:57:45 -07:00
Matthew Meszaros c5a981d86c feat: record a warmup deletion strike only after the worker searches the mailbox and finds the message in the trash or gone, so a move reported as a removal (Graph, a provider filter, a mailbox rule, a second Warmbly instance, our own filing) is never charged, withdraw strikes whose message is still in the mailbox and lift the pause or block they imposed, recheck deletion strikes recorded before the search once via warmup_tampering_events.verified_at (000229), and clarify the tampering reasons and warmup guide 2026-09-29 04:28:34 -07:00
Matthew Meszaros 014ba06b3a feat: weight warmup partners by the sender's verified spam rate per recipient mail host instead of per address domain, report deliverability warmup placement and the placement_in_spam webhook by mail host, mark IMAP warmup mail not-junk before it leaves Junk, send single-part text mail over SMTP with a fixed header order, and update the warmup, deliverability, analytics, webhook and OpenAPI docs 2026-09-26 06:30:22 -07:00
Matthew Meszaros 6a13fd5238 feat: report the Message-ID Gmail stamps on a send and resolve a reply's campaign through the Gmail thread and In-Reply-To so campaign replies stop tagging as sales pitches (with a warmblyctl --recheck-cold-inbound backfill that clears the stale labels and a tasks thread index), let a workspace add its own yes/no and pick-one tagging questions with plain-word labels and hold/stop/task actions, and add tagging languages (44 named to the classifier; for 27, only once chosen, tagging also cuts their quoted history and reads their away and non-delivery subjects), documented in the inbox tagging guide and the OpenAPI schema 2026-09-24 21:08:35 -07:00
Matthew Meszaros 256010725b feat: count only email steps in every sent total (contact drawer, Leads view and statuses, campaign progress, guardrails, org conduct, verification evidence, segments, admin, advisor), keep line breaks in synced body text and strip quoted history in any shape, classify delivery failures before out-of-office and tag them as bounces, record statusless failure notices from X-Failed-Recipients as permanent bounces, limit reply and inbox-tag opt-outs to people answering our outreach (never bounces, auto-replies or list mail), and recheck earlier reply opt-outs, lifting with an audit entry only those the message that wrote them no longer supports 2026-09-24 09:44:17 -07:00
Matthew Meszaros 7557d97866 feat: run a saved mailbox's placement, events, webhook and load on their own context so an expired caller deadline cannot skip them, bound bulk connects per row instead of per batch, have the worker reconciler remember the worker a load actually placed or moved a mailbox onto, keep an ADD_EMAIL that follows a REMOVE_EMAIL while the earlier load is still dialing by running it after that load, and show an import waiting only on vendor authorization as being authorized rather than importing 2026-09-24 11:58:49 +02:00
Matthew Meszaros 549fb00be6 feat: keep mailbox credential checks and imports from timing out behind a worker's command queue by loading mailboxes off the bus loop (a republish never dials twice, commands wait for an in-flight load, a failed load raises the account's auth or server error), storing Kafka offsets for background commit instead of a synchronous commit per message, reconciling moved, unplaced and dead-worker mailboxes at once while spreading the safety-net republish over 30 minutes, sending checks over each worker's Redis channel with failover in placement order, retrying unanswered import rows within the lease, finishing a connect the browser left and an import row a restart interrupted, and connecting InboxKit Google and Microsoft mailboxes with no sign-in through a vendor-authorized grant that covers only the domains the vendor account lists 2026-09-24 11:44:58 +02:00
Matthew Meszaros e58921484d feat: rebuild mailbox import around column mapping and automatic host and sign-in detection (CSV, XLSX, pasted lists, saved mappings, retryable rows with fixes, migrations 000205-000206), connect whole Google Workspace domains and Microsoft 365 organizations through a proved administrator grant, import from inbox vendors (InboxKit, Zapmail, Mailforge, Infraforge, Maildoso, Cheap Inboxes, ScaledMail) with vendor-managed forwarding and DNS, add a sending domains page with per-domain tracking and verified root redirects, unify Add account into one Google and one Microsoft entry with per-method choices, mark per-mailbox Google sign-in as retiring with in-place moves to the admin grant or an app password, allow the loopback security mode in the credential columns (migration 000207), read semicolon-separated CSVs, and add a mock vendor API to the sandbox 2026-09-23 08:41:01 -07:00
Matthew Meszaros 1520da2fa8 feat: look up moved mail against each skipped folder with one SELECT per folder and cap the reconciliation by searches rather than rows, never settle a row whose lookup failed, refuse a skip name that is a saved folder the matcher keeps and purge only what it will stop following, drop the map entries of parked arrivals too, ignore UIDVALIDITY 0 when matching a rename into the skipped subtree, carry the listing memo and pending mark across a rename, ask before ticking a folder in the drawer since imported mail is removed, share one inbox-deleted publisher between the consumer's removal paths, and say in the CLI help that an emptied list does not restore removed mail 2026-09-22 05:45:38 -07:00
Matthew Meszaros ee9f5bf84e feat: make every skipped-folder purge drop the message map entries and parked arrivals with the rows so a message can be imported again if it moves back, persist each folder's delimiter (migration 000197) so the backend purge matches the same subfolders and case the worker skips, fail the mailbox load closed when the skip list cannot be read, mark a folder renamed into the skipped subtree as skipped, never remove a row re-fetched this pass, cap the reconciliation at 50 searches per pass and continue next pass, publish one EMAIL_DELETED after a folder purge, and resolve the account once for GET /emails/:id/sync 2026-09-22 05:33:56 -07:00
Matthew Meszaros ad353b385a feat: key the IMAP departure check on the previous listing's count and UIDNEXT per folder instead of the stored cursor, since a walked folder's cursor now advances to the SELECT view and a server whose view lags its STATUS would read as departures every pass; the fake listing in the worker tests returns a copy so a second pass still sees the skipped folder 2026-09-22 05:20:06 -07:00
Matthew Meszaros 9a7ef088cf Merge remote-tracking branch 'origin/main' into feature/imap-folder-exclusion 2026-09-22 05:17:37 -07:00
Matthew Meszaros 5a967cc3ce feat: let an IMAP mailbox exclude folders from sync (email_accounts.sync_skip_folders, migration 000196) so a folder another tool fills never reaches the unified inbox: the worker drops skipped folders and their subfolders before the walk, retires an already-synced one with its stored mail, and removes mail that later moves into one only when its Message-ID is found there; PUT /emails/:id/sync and the drawer's Sync card set the list, GET reports it with the server's folder list, warmbly mailbox skip-folders mirrors it, with docs, OpenAPI and error-code invalid_sync_folder 2026-09-22 05:15:49 -07:00
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