Matthew Meszaros
75f764dc67
feat: require a verified Cloud Tasks token with a pinned audience on task webhooks, manage_settings on integration OAuth and a fresh membership check at every mailbox and integration OAuth finish, user verification for passkey sign-in, ended sessions before a password reset or ban reports success, and a revoked session when a rotated refresh token is replayed; remove the internal DEK delete route, log credential path parameters by name, hold remote images in received mail until the reader loads them, add Calendly and Cal.com signing keys with inbound URL rotation, keep automation signing secrets out of connection responses, and apply the password rules to the bootstrap password
2026-09-30 06:46:24 -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
208481f9a4
feat: answer a signup for an existing address with 409 conflict and let a concurrent SSO first sign-in resolve against the account it raced, adopt the stored organization DEK when a parallel first use stored one first, classify IMAP SERVERBUG, LIMIT, bare read-command failures and provider ALERT/EXPIRED responses as server-unreachable, throttle or credentials errors and keep the ALERT text, wait up to two minutes for a free connection slot before the boot migration gives up, guard the dashboard's DOM mutations against browser page translation, treat an aborted passkey sign-in as a cancellation, and drop posthog-js request timeouts from error tracking
2026-09-27 08:26:55 +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
c35e05c9eb
Merge pull request #652 from warmbly/feature/sending-window-timezone
...
feat: give each workspace a timezone that mailbox warmup and campaign sending windows follow by default, with a Timezones control centre on Settings > Profile and the first-email delay moved into campaign Settings
2026-09-23 03:42:59 +00:00
Matthew Meszaros
9b47f91e89
feat: name a request-body time that is not RFC 3339 in the bind refusal (documented in the error-codes table) and cover warmbly campaign add-step and edit-step in the CLI body test against the sequence update struct
2026-09-22 20:35:15 -07:00
Matthew Meszaros
6cadcc725d
feat: answer every public request-body bind failure with a 400 that names the problem (empty body, JSON syntax error with its byte offset, wrong JSON type for the body or a named field, missing or out-of-range fields by json key) instead of a blanket malformed-JSON message or a 500, accept a single contact object on POST /v1/contacts as the CLIs send it, and drop the API-key lookup cache whose 300ns TTL made it a Redis round trip that saved nothing
2026-09-22 20:21:41 -07:00
Matthew Meszaros
45c045a5bb
feat: give each workspace a timezone that mailbox warmup and campaign sending windows follow by default (organizations.timezone, migration 000198), let campaigns and mailboxes follow it or pin their own, add a Timezones control centre on Settings > Profile with inline per-campaign and per-mailbox zones, default new workspaces and the campaign wizard to the browser zone, expose effective_timezone on campaigns, and move the first-email delay from the Schedule tab and wizard into campaign Settings > First email
2026-09-22 20:13:55 -07:00
Matthew Meszaros
a1b7a8ad9b
feat: attach a parked federated identity only after the ban check and, on a 2FA account, only once the second factor passes by carrying it through the 2FA pending record into twofa.VerifyLogin, charge each sso_link password attempt atomically before the check, treat an identity a parallel challenge already linked as a re-login instead of a refusal, ask for no password when the identity cannot be linked, end an exhausted or expired challenge with sso_link_expired so the dashboard returns to the email step, and document the code in error-codes, the API reference and OpenAPI
2026-09-21 03:35:17 -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
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
f9db4f435f
feat: actually deactivate a mailbox whose credentials have died, by writing its status against the mailbox id instead of through the organization-scoped update that was being handed the owner's user id and so matched no row, and stop labelling a mail error's own uuid as an email account in the message
2026-09-19 20:23:17 +02: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
b09ec39907
Merge pull request #599 from warmbly/chore/casa-al1-security-assessment
...
feat: complete the ADA CASA AL1 control set and ship the assessment evidence pack
2026-09-19 06:39:12 +00:00
Matthew Meszaros
e668a2a36b
feat: complete the ADA CASA v2.1.1 AL1 control set across authentication, sessions, access control, cryptography, input validation and configuration, adding a breached-password denylist and per-account login throttling, enforced multi-factor authentication on the admin panel, step-up confirmation before an action that mints a lasting credential, purpose-scoped session tokens, single-use TOTP steps, tenant verification on every cross-referenced identifier, security headers on every surface, encrypted webhook signing secrets, per-organization idempotency, PKCE and a minimal two-scope Gmail consent on the mailbox OAuth flow, bounded spreadsheet and archive decoding, a patched Go toolchain with govulncheck in CI, and the evidence pack under compliance/casa
2026-09-19 08:18:35 +02:00
Matthew Meszaros
6428e6b3e9
Merge pull request #594 from warmbly/feature/disable-google-oauth-new-mailboxes
...
feat: route new Gmail mailboxes through a guided app-password connect instead of Google sign-in, behind BOX_GOOGLE_OAUTH_CONNECT, leaving existing OAuth mailboxes sending and re-authorizable
2026-09-19 06:11:15 +00:00
Matthew Meszaros
49acd51b64
feat: stop one recurring fault burying error tracking by reporting it once per five minutes with the count it stands for, keep a cache outage from answering every signed-in request with a 500 and from taking realtime down by treating an unreachable Redis as a miss and the websocket handshake nonce nothing reads as best-effort, answer a 5xx with a sentence the reader can act on while the call site's own words go to the log against the same request id, prefer the API's own message over the HTTP class in the admin and dashboard clients, and name the fix on a schema registry refusal, an SES sandbox rejection and a mailbox check that could not be run
2026-09-19 07:39:40 +02:00
Matthew Meszaros
ee46cb49e8
feat: route new Gmail and Google Workspace mailboxes through a guided three-step app-password connect over smtp.gmail.com and imap.gmail.com instead of Google sign-in, behind BOX_GOOGLE_OAUTH_CONNECT (off by default) and announced to clients as gmail_oauth_connect on /auth/config, refusing a new gmail OAuth start with 403 mailbox_gmail_oauth_disabled in both the direct and Warmbly Cloud broker paths while leaving mailboxes already connected that way sending, syncing and re-authorizable
2026-09-18 22:06:09 -07:00
Matthew Meszaros
e37c5053c2
feat: make warmup refunds atomic, count confirmed partner diversity in local and cloud mailbox views, and document cloud-safe mailbox deletion
2026-09-17 21:15:28 -07:00
Matthew Meszaros
177a0817c4
Merge remote-tracking branch 'origin/main' into fix/closiqode-reported-issues
2026-09-17 21:00:48 -07: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
8ee1c50a1b
feat: make a failed SMTP send name the step and the cause behind it instead of one bare SERVER_UNREACHABLE sentinel, give a refused warmup send its day back so sent_today can no longer climb past the target while the cap frees the slot, revoke a mailbox's Warmbly Cloud enrollment when it is deleted so the pool stops holding its password, and prefer warmup partners outside the sender's own workspace while showing the partner diversity a mailbox is actually getting ( #574 , #575 )
2026-09-17 20:02:37 -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
Matthew Meszaros
95885fa714
fix: save a contact's edited email address ( #511 ) ( #521 )
...
* fix: save a contact's edited email address by giving models.UpdateContact the Email field the dashboard was already sending, normalizing it to a bare lowercased address, refusing one another contact in the workspace holds with a 409 contact_email_taken instead of letting the unique index 500, and dropping the verification verdict plus the delivery evidence that belonged to the old mailbox (issue #511 )
* fix: reset a contact's verification evidence behind a watermark the delivery-credit job honours, clear the cached esp_provider the old domain produced, write a case-only edit in place instead of answering 200 and changing nothing, and normalize the address on create and import too so the two paths cannot disagree about what an address is
* fix: refuse a verification observation whose campaign step provably left before the contact's address was edited, so a hard bounce or an open for the old mailbox arriving afterwards cannot mark the corrected address invalid or valid, read the credit watermark against dispatched_at rather than the sent_at a worker result stamps later, and answer a racing address collision with the documented 409 instead of the unique index's 500
* fix: build the deliverability evidence step from the resolved campaign task so both halves name one real row, drop the email format hint from the OpenAPI schema now that the endpoint also accepts a display-name address, and drain the shared delivery-credit backlog in the live test instead of assuming one pass reaches this contact
2026-09-14 23:05:16 -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
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
d456bc48c6
feat: fix the Warmbly Cloud pool link across both roles ( #262 ): take an enrolled mailbox out of this instance's own warmup pool so local partners stop writing to it and their unverifiable warmup stops landing in the owner's unibox, recognise the cloud's warmup mail whose verify header did not survive delivery through a new warmup-deliveries lookup that ignores consumed_at because instance and cloud read the same mailbox, move the managed-mailbox access token route behind NODE_BROKER_TOKEN so the internet-facing tracking and forms services can no longer mint a live provider token, scope pause and resume to the caller's workspace, keep an enrolled mailbox listed once it goes inactive, release the cloud copy when the local mirror row cannot be written, refuse the one-time handshake when CREDENTIALS_ENCRYPTION_KEY is missing, blank an expired code's plaintext instance token, and stop errx answering 200 for a status outside its table
2026-09-12 06:58:25 -07:00
Matthew Meszaros
da4b89da0b
feat: split a unibox message's provider placement into its own provider_folder column (migration 000146) so Archive and Delete in the thread header survive the next sync without the sync losing the ability to follow a real provider move, and narrow PATCH /unibox/folder to inbox/archive/trash behind the unibox feature gate with an audit entry so the move reaches every teammate's list live
2026-09-11 03:23:19 -07:00
Matthew Meszaros
9ce1e060dd
feat: fix the campaign step body editor for issue #380 by registering TipTap's UndoRedo and HardBreak so Ctrl+Z and Shift+Enter work at all, normalising pasted HTML from Gmail/Outlook/Word so their blank-line scaffolding stops rendering a second gap on top of our paragraph margins, adding a workspace email image library (migration 000139, public email-images/ objects sharing the attachment storage quota, keyset-paginated /email-images endpoints) with toolbar upload, URL and library insertion, drag-drop, clipboard paste and a size/alignment/alt bubble, and adding an HTML source view that names the tags the schema would drop before switching back
2026-09-08 21:00:09 -07:00
Matthew Meszaros
766bd3ae6a
feat: let a self-hosted instance connect a mail server on its own machine by adding a third mailbox security mode, "none", accepted only for a loopback literal and only where the worker shares a host with the relay, so Proton Bridge on 127.0.0.1:1143/1025 and a local Dovecot or Mailpit can be connected at all, with the rule enforced in onboarding and reauth validation, again by the worker against the peer it actually dialled rather than the name it was given, and hidden from the connect form on the hosted product where the worker is never the customer's machine
2026-09-07 08:07:40 -07:00
Matthew Meszaros
4582896f54
merge
2026-09-07 04:43:45 -07:00
Matthew Meszaros
6bfba363d3
feat: address the CodeRabbit review on the SMTP compatibility PR by carrying the server's own refusal in a rejected-recipient error so an address that no longer exists is distinguishable from one a policy blocked, classifying our own refusal to authenticate over an unencrypted link as the configuration problem it is rather than retrying it four times as an outage against a server that is answering fine, and fixing the fake server's canned refusal at construction so the session goroutine and the test no longer race on it under go test -race
2026-09-07 04:30:04 -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
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
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
Matthew Meszaros
3596a9770f
feat: make mailboxes truly unlimited under a fair-use allowance of one mailbox per daily send the plan includes (15,000 on Business), drop the 200 hard cap and the 5-per-day connect throttle, open a request-more dialog with inline limit requests and plan upgrade when the allowance is reached, add bulk CSV mailbox import with live progress and a downloadable failed-rows file, reserve attachment storage atomically under a per-org lock so concurrent uploads cannot pass the quota ( #326 ), report storage and the allowance on the billing usage meters, and honor approved daily-send overrides in the sender
2026-09-04 21:00:26 -07:00
Matthew Meszaros
d6ddf1f170
feat: fix implicit-TLS SMTP on 465 and IMAP STARTTLS on 143 behind a stored per-mailbox security mode that accepts any port, stop worker ID churn orphaning mailbox assignments via flock-claimed persistent worker ids, give the unibox a standard mail-folder sidebar (inbox/sent/drafts/archive/spam/trash) backed by a provider-derived folder column, and expose the AI tool registry over REST for non-MCP function-calling agents ( #283 )
2026-09-01 03:53:19 -07:00
Matthew Meszaros
773a3ecaf2
Merge remote-tracking branch 'origin/main' into fix/account-reverification-button-missing
2026-08-31 06:26:40 -07:00
Matthew Meszaros
c2f5cc4e9e
feat: address review on the reconnect flow: a reauth whose repeat consent omits the refresh token now refuses (typed error) when the stored one cannot be read instead of sealing an empty string over it, and credential errors resolve only after the reactivation Update succeeds so a failed reactivation keeps the banner and its reconnect button
2026-08-31 06:26:39 -07:00
Matthew Meszaros
896fc60f4d
feat: add the missing mailbox re-verification flow (issue #274 ): POST /emails/onboarding/oauth/reauth/:id re-runs the provider consent for an existing Gmail/Outlook mailbox (login_hint preselect, same-address check, token rewrite keeping the stored refresh token) and PUT /emails/onboarding/smtp-imap/:id replaces SMTP/IMAP credentials after live validation; both resolve the credential-class error rows and reactivate the mailbox onto its worker, the drawer's Needs attention banner gains Re-authorize and Update credentials buttons, and the mailboxes guide documents reconnecting
2026-08-31 05:29:35 -07:00
Matthew Meszaros
d7a17a0149
feat: make the per-mailbox daily campaign cap configurable up to 5000 (issue #276 ): raise campaign_limit, campaign daily_limit and ramp start/ceiling validation to config.LimitMax, warn in the dashboard above 100/day, and update aitools, zapier and docs copy to match
2026-08-31 03:50:36 -07:00
Matthew Meszaros
152f626469
feat: remove the free trial: a new hosted workspace starts on the free plan with no time limit, may hold up to 10 mailboxes (connected directly or through a linked self-hosted instance) and warm them in the free pool, and everything else (campaigns, inbox, contacts, CRM, integrations, API keys, audit) sits behind a Starter lock in the sidebar and a SubscriptionGate overlay until a plan is active; the pool-link allowance now counts every mailbox in the workspace, and the marketing site and guide say free workspace instead of 14-day trial
2026-08-29 08:34:22 -07:00
Matthew Meszaros
1b8c51ad06
feat: read spam complaints and domain-auth refusals, the last two open delivery signals ( #232 )
...
* feat: close the two halves of the delivery-signal loop that were still open, complaints and domain-auth refusals: a spam complaint arrives as mail long after the send succeeded, and nothing read those reports, so the strongest negative signal a sender gets never reached the complaint rate, the suppression list or the breaker; internal/pkg/arf parses RFC 5965 feedback reports worker-side alongside the existing DSN path, takes the LAST Message-ID because the reported mail's headers follow the report's own, and records only abuse-type reports so a not-spam report cannot be inverted into a complaint; separately a receiving server refusing mail because the SENDING DOMAIN failed its authentication (5.7.515, 5.7.26) was classified as SERVER_UNREACHABLE and retried forever, and it is now its own hard code that blames the domain rather than the address, leaves the recipient unsuppressed, and brings that domain's DNS re-check forward so the sweep confirms the verdict the send gate acts on
* feat: make the complaint path actually reachable, and stop it trusting the report: selectTextParts only ever picked text/plain and text/html, so an RFC 5965 report's message/feedback-report and message/rfc822 parts never reached the worker and both Feedback-Type and the reported Message-ID were invisible, which would have left this feature inert and has been quietly weakening DSN parsing too; report parts are now selected as plain text; the complainer is taken from the RESOLVED SEND rather than the report body, because a report is unauthenticated mail anyone able to reach the mailbox could forge and honouring the address it names would let a forger suppress a contact the send never went to; Original-Mail-From is no longer read as the complainer since that address is the sender; and a domain-auth refusal now releases the reservation instead of spending one of the lead's attempts, because the recipient received nothing and the problem is the mailbox's domain, so another mailbox in the pool picks the lead up
* feat: fix the same missing-parts gap in the Gmail adapter, and say plainly that Microsoft Graph cannot see reports at all: goog.extractBody took only text/plain and text/html exactly as the IMAP path did, so a feedback report synced from Gmail was as invisible as one synced over IMAP, and the tests that would have caught either called the parser directly rather than going through the adapter where it actually broke; the new tests exercise that seam, and Graph returns one rendered body with no parts so reports there are undetectable without a MIME fetch that is not built, which the docs now state rather than implying full coverage
2026-08-28 11:02:45 -07:00
Matthew Meszaros
029bc27bfd
feat: bind a browser sign-in to the browser that started it, so a handoff link cannot be forwarded: one-time state proves the callback answers a request this server made, not one THIS browser made, so anyone could run the flow against their own Google or OIDC account and send the resulting URL to someone else, whose browser would then hold the sender's session (RFC 9700 4.7.1); begin now mints a binding secret that never reaches the provider and never appears in a URL, the callback carries it into the handoff, and the exchange refuses a collection that cannot present it with sso_wrong_browser, while the comments this PR added are condensed to the constraint they exist to state
2026-08-28 01:44:39 -07:00
Matthew Meszaros
baa5f89045
Merge branch 'main' into fix/issue-219
2026-08-28 00:56:13 -07:00
Matthew Meszaros
bd435a2687
feat: tell the worker to drop a mailbox the customer disabled or disconnected, the two paths #217 left out: emailService.Update now publishes REMOVE_EMAIL when a status leaves active (and re-ships the mailbox when it returns to active instead of waiting on the reconciler), Delete publishes it before the row goes and refuses to delete when it cannot, since after the row there is no assignment left to read; delete also stopped failing outright, because tasks and warmup_admin_actions referenced email_accounts with no delete action so disconnecting anything that had ever warmed up or sent a step raised a foreign key violation (migration 000098), read the mailbox through an org-scoped query while passing a user id so the warmup pool cleanup, realtime event and webhook never ran, and never refunded the worker's account count or load score
2026-08-28 00:40:39 -07:00
Matthew Meszaros
e6210dff09
feat: stop a transient Graph failure on a folder's first backfill page from marking that folder's backfill permanently complete: msgraph.HandleError mapped 404, every 5xx and any unrecognised status onto the same ErrMailServerUnreachable, so graphBackfill's "the tenant does not have this folder" skip fired on a 503 as well and wrote the folder off in a cursor that is persisted through SYNC_STATE and handed back on every later load, silently costing a customer who connected a mailbox during a Graph incident their archive history; 404 now carries its own RESOURCE_NOT_FOUND code, the skip keys on that alone, a genuine unreachable server ends the pass with the folder's cursor held so the next one retries it, and the Gmail and IMAP imports get the same regression shape pinned by tests
2026-08-28 00:40:18 -07:00
Matthew Meszaros
66bd9e54e2
feat: route every contact custom-field write through one normalizeCustomFields helper in pg_contact so Add, Update and the bulk field editor all trim and whitespace-collapse a key before storing it and answer a bad one with the name and the rule instead of the opaque errx.ErrJSONKey, which is now unused and deleted, and bind the custom-field search filter key as a query parameter rather than interpolating it into the JSONB ->> operand now that a key can legitimately contain spaces
2026-08-27 03:43:24 -07:00