Commit Graph
840 Commits
Author SHA1 Message Date
Matthew Meszaros f99ee57484 feat: scope mailbox disconnect to the workspace instead of the connecting member, so a teammate with manage_emails no longer gets 404 on a mailbox the list shows them, delete by id in the repository on the strength of that check while the worker removal still names the owner the consumer's unibox cleanup is keyed on, and read the API's own reason off the normalised AppError in the accounts page so a refused disconnect says why instead of "The mailbox couldn't be disconnected" on every failure 2026-09-19 06:01:03 +02:00
Matthew Meszaros 81d46bdcc8 feat: attribute a campaign reply by the thread it answers rather than requiring the From address to equal the contact's, so a person replying from a Gmail send-as alias, a forward or a colleague's desk counts as replied for that lead, stamp replied_at whether or not reply-intent automation is switched on, suppress the mailed address too when an alias reply opts out, and count only a person's opens in every open count and open rate (campaign overview, per step, daily, hourly, dashboard, recent activity) with machine opens shown as a separate not-counted figure, matching how the Leads tab already reads opened 2026-09-18 14:29:16 +02:00
Matthew Meszaros 94482df9d1 Merge pull request #589 from warmbly/fix/campaign-progress-and-daily-budget-visibility
Fix campaign progress counts, the stuck Sending card, and explain a spent daily budget per mailbox
2026-09-18 12:08:03 +00:00
Matthew Meszaros 7e6cbd6b1f feat: count the send in flight on the last-email-of-the-day feed line so a cap-one mailbox is shown at its cap with a budget gate rather than at zero, and put live campaign progress in emails (contacts times steps) with a total_emails field so a six-step campaign no longer reads 100% once every contact has had its first email 2026-09-18 13:54:12 +02:00
Matthew Meszaros 5b96ce903b feat: count campaign progress from each table on its own so one sent email is no longer multiplied by leads times steps into 378 of 63 contacts at 100%, show the live Sending card only while a named contact's email is in flight and close it on EMAIL_SENT instead of leaving Sending Unknown contact up all day, and write the day's last send and every budget-spent line to the campaign feed with a per-mailbox breakdown of the cap, the clamp that set it, sends today, the gate and warmup health band so a campaign sending one email a morning says why 2026-09-18 13:12:07 +02:00
Matthew Meszaros cd964aafa8 feat: stop a deleted mailbox living on inside its worker, by returning the worker assignments a scheduled org or user deletion destroys so the erasure path can evict them the way the single-mailbox delete already does, and by treating an assignment lookup that finds no row as the mailbox being gone rather than a failed lookup, which tells every live worker to drop it; and clear the rest of error tracking by retrying a mailbox delete that loses a deadlock to its own cascade instead of failing the person who clicked Disconnect, answering a daily-usage read for a mailbox that no longer exists with 404 rather than a reported 500, and guarding the inner data field on three list reads plus serialising an empty pool-link list as [] rather than null 2026-09-18 12:42:43 +02:00
Matthew Meszaros 0e914ee390 feat: stop the password reset flow reporting success while sending nothing, by answering 200 only for an address with no account rather than for every cache and database fault, folding addresses to one case on every account lookup and write so a typed capital cannot miss the row or split an SSO account in two, refunding the two-per-four-hours budget when the failure was ours, retrying one transient send and quoting the link's real lifetime; and clear the rest of error tracking by grouping the engagement breakdown in a subquery so ORDER BY stops resolving opens against email_opens, dropping unibox_mailboxes and email_sync_state writes whose mailbox was deleted mid-sync instead of redelivering them forever, answering a corrupt argon2 hash with ErrCredentials rather than a 500, never filing a cancelled caller as a database incident, and reporting only the first websocket init failure of a streak 2026-09-18 11:42:49 +02: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 d739f449e3 feat: make the unibox search persistence regression deterministic by keeping the opened subject inside the debounced result set 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 d2095a8a70 feat: stop a greylisted RCPT reply from being filed as bounce evidence now that the send carries the server's own words, revoke a cloud enrollment after the worker removal and put the mailbox back when the revocation is refused so a failed delete really changes nothing, drop the unenroll-under-Settings advice that makes the same failing call, and only log a within-workspace pairing when there is a sibling to draw 2026-09-17 20:19:46 -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 fdf7033c70 feat: correct the Gmail app-password instructions in the connect dialog and mailboxes guide, since Google removed the IMAP setting in January 2025 and app passwords are unavailable under Advanced Protection, security-key-only 2SV or an admin policy 2026-09-17 20:02:07 -07:00
Matthew Meszaros 1a73ff4bb3 feat: say in the Gmail connect warning and the mailboxes guide that an app in review is capacity capped, so a mailbox connected through Google sign-in may be disconnected later and need reconnecting 2026-09-17 19:51:23 -07:00
Matthew Meszaros 97e19bcb81 feat: mark Gmail OAuth as not recommended in the connect dialog with a red badge and an explanation dialog that routes to SMTP/IMAP with Gmail app-password steps, and document the same in the mailboxes guide 2026-09-17 19:51:23 -07:00
Matthew Meszaros 6f0314081c feat: update vulnerable dependencies across Go Phoenix docs site and web 2026-09-17 07:04:40 -07:00
Matthew Meszaros deeef293e4 feat: render automatic inbox label explanations with themed tooltips 2026-09-17 05:02:58 -07:00
Matthew Meszaros b733d09f89 feat: make inbox follow-up labels safe for manual labels and automated mail 2026-09-17 04:57:52 -07:00
SUMAN JANA 3b8761d361 feat(inbox): explain every tag on hover, label live mail, and keep follow-ups working with no external service 2026-09-17 04:57:52 -07:00
Matthew Meszaros dcf26a2361 feat: make automatic inbox tagging atomic live and reviewable in production 2026-09-17 04:57:24 -07:00
SUMAN JANA 260898bf20 feat(inbox): automatic tagging and relevance scoring for inbound mail, optional and off by default 2026-09-17 04:57:24 -07:00
Matthew Meszaros ae1a324801 Merge pull request #562 from rocker1166/feat/direct-mail-analytics
feat: add accurate direct-mail analytics and opt-in engagement tracking
2026-09-17 11:47:50 +00:00
Matthew Meszaros 93a0e39371 Merge pull request #568 from warmbly/fix/reported-issue-resolution
feat: preserve binary dashboard API responses so campaign lead exports remain downloadable
2026-09-17 11:28:29 +00:00
Matthew Meszaros 8745dd988d feat: correct direct-mail analytics attribution and publish live engagement updates 2026-09-17 04:27:00 -07:00
Matthew Meszaros 7402898c0d feat: preserve binary dashboard API responses so campaign lead exports remain downloadable (#565) 2026-09-17 04:24:52 -07:00
Matthew Meszaros cc5925fe44 feat: preserve web-search operators and escape participant patterns in unibox search 2026-09-17 04:18:08 -07:00
SUMAN JANA 2134c7a143 feat(analytics): report on mail written by hand, with opt-in open and click tracking per mailbox 2026-09-17 10:26:25 +00:00
SUMAN JANA ebbe432d2c feat(unibox): search people and partial words, and offer to widen a search that found nothing in the current scope 2026-09-17 10:20:17 +00:00
Matthew Meszaros 0f5ca71155 feat: correct mailbox sending metrics and workspace analytics across dashboard surfaces 2026-09-16 21:44:42 -07:00
Matthew Meszaros be78d46e28 feat: hide the self-host nudge on the hosted Accounts page for subscribed workspaces and offer subscribing alongside self-hosting on free ones in CloudPathsPanel 2026-09-16 19:53:35 +02:00
Matthew Meszaros 0334d2c64a feat: prevent ResizeObserver feedback warnings from reaching PostHog and scope worker outage alerts per workspace on shared cloud workers 2026-09-16 17:42:38 +02:00
Matthew Meszaros 6872ebbb23 feat: fix dashboard Stripe checkout, portal eligibility, webhook recovery and audit rendering 2026-09-16 15:23:17 +02:00
Suman Jana 94c7e414e2 feat: preserve private Unibox reply drafts and safely collapse quoted conversation history 2026-09-16 04:35:53 -07:00
Matthew Meszaros f72d1f8d5c feat: prevent sender views from tracking opens and keep sent messages out of the default Inbox (#542) 2026-09-16 03:42:58 -07:00
Matthew Meszaros 16df37d97b fix: stop the unibox reply composer wiping what you are typing (#534)
* feat: stop the unibox reply composer clearing what is being typed, by resetting only on a restore seed instead of on values derived from replyTo, which the thread rebuilds on every render, so any realtime invalidation while a thread was open wiped the draft between keystrokes and a reply could not be written at all

* feat: drop the stray blank line left where the per-render messages build used to sit in ThreadView
2026-09-15 09:41:54 -07:00
Matthew Meszaros ae012dd13f Clear the live error-tracking issues, and the workspace rename that renamed the wrong workspace (#533)
* feat: stop a managed Kafka cluster refusing topic creation from failing the publish, by treating a topic- or cluster-authorization failure from CreateTopics as a topic the cluster owns rather than one that is missing, which on Confluent Cloud dropped every warmup event and filed one issue per message because the topic never became known

* feat: drop a report whose error is a cancelled context in errs rather than at ninety call sites, so a browser navigating away or a container draining on deploy stops filing one issue per query that happened to be in flight, while a deadline this process set and blew through still reports

* feat: stop renaming one workspace from renaming another, by keying the workspace settings editor on the workspace id so a switch re-seeds the name field instead of leaving the previous workspace's name against the new workspace's autosave baseline, and pinning every save on the workspace, sending and tracking pages to the workspace its draft was hydrated from

* feat: drop Script error. and the ResizeObserver notice on the marketing site and the hosted form page the way the dashboard and admin panel already do, since those two carry no stack and no bug and between them were the largest issues in error tracking, all of it from warmbly.com

* feat: rename the forms Turnstile script module to turnstileScript.ts so it no longer differs only in case from the Turnstile.tsx component, which resolved both imports to one file on a case-insensitive filesystem and failed forms' typecheck with TS1149

* feat: upload source maps from the static build:pages build as well as the image build, so the dashboards served from a static host stop reporting every stack frame as a minified name beside 'Invalid source map: bad json', which is PostHog falling back to fetching the .map from a host that answers with its SPA fallback

* feat: build every admin list in pg_admin.go with make rather than declaring it nil, so an empty page serializes as [] instead of null, and guard the audit table's own empty check, which is what crashed admin.warmbly.com/audit with 'null is not an object (evaluating d.data.length)' whenever a filter matched nothing

* feat: match the whole broker description rather than a substring when deciding a topic create was refused for permissions, since that answer remembers the topic as present, and clear the cached promise and dead tag when the forms Turnstile script fails to load so a blocked first attempt no longer leaves every later mount with the same rejection and the captcha permanently missing
2026-09-15 09:05:53 -07:00
Matthew Meszaros c4c58cc116 feat: report a failed dashboard socket handshake with its actual error, status, code and request id instead of the plain AppError object that console.error rendered as '[WS] Init failed: [object Object]', and keep an offline blip or an already-expired session a warning so only an unexpected answer reaches error tracking (#532) 2026-09-15 05:46:07 -07:00
Matthew Meszaros e2487296d6 feat: rename the campaign entry-delay picker to EntryDelayPicker.tsx and update its three importers, so the component no longer differs only in case from the entryDelay.ts vocabulary module, which resolved every import of it to the wrong file on a case-insensitive filesystem and failed web's typecheck with ten errors 2026-09-15 13:39:05 +02:00
Matthew Meszaros 8740558ffa feat: stop the dashboard socket's onerror handler reporting every WebSocket error event to PostHog as an exception, since the event carries no detail by spec and onclose already drives the reconnect, so a deploy or a laptop sleep logged '[WS] Error: [object Event]' through capture_console_errors 2026-09-15 13:39:05 +02:00
Matthew Meszaros e67b13e57e feat: stop reporting a mailbox's DKIM as missing when its selector was simply never probed, by deriving candidate selectors from the sending domain's own SPF and MX records on top of a wider default set, reporting a miss as the tri-state dkim_status undetermined rather than a red Missing row in the drawer, dropping DKIM from the Advisor's missing-records finding entirely, refusing a revoked p= key, holding the summary back from accusing anything when DNS never answered, and fixing the CLI auth-check table whose columns read mailbox fields the endpoint does not return (#528) 2026-09-15 02:27:38 -07:00
Matthew Meszaros d40a95dff5 fix: give the dashboard's auth errors a real stack and stop reporting an ended session as a crash, by building AuthError per throw instead of sharing two module-level instances whose stack was captured at module evaluation, so every report pointed at "module code" rather than the call that failed, and by dropping AuthError in before_send since normalizeError already turns it into a redirect and UserProvider sends the user to sign in (#527) 2026-09-15 01:52:35 -07:00
Matthew Meszaros dd98187231 fix: shorten recipient unsubscribe links to 22-character, 128-bit stored tickets (#498) (#525)
* feat: shorten every recipient unsubscribe link from a 96-character signed token to a 22-character stored ticket carrying 128 bits from crypto/rand, minted once per recipient per campaign and reused by every step, so the address the text/plain half of a cold email prints in full fits on one line and cannot be guessed, keeping the signed form working for links already in inboxes and as the fallback when the store cannot be written, and answering a failed lookup with a retryable 'try again shortly' instead of telling the recipient their opt-out is invalid (issue #498)

* fix: restore the disabled-signer guard in URLOn, which factoring the URL builder moved behind a token mint that dereferences the signing key, so a nil or origin-less signer returns the empty string every caller reads as 'no link can be minted' instead of panicking (PR #525 review)
2026-09-15 00:42:45 -07:00
Matthew Meszaros 50711a8e66 fix: a campaign's linked segments are its audience, so detaching one withdraws its leads (#510) (#523)
* fix: make a campaign's linked segments the audience rather than an accumulator, so detaching one withdraws the leads it enrolled instead of leaving the old list mixed in with the new, tracked by a new campaign_leads.source that keeps a hand-picked lead, an overlapping segment's member and anyone the campaign has already emailed out of the withdrawal, and reported back as withdrawn/contacted counts the dialog confirms and explains (issue #510)

* fix: serialize the linked-segment sweep against a link replacement by taking the same campaign lock, so a sweep that read the old set cannot re-enrol the audience the replacement just withdrew, and word the dialog's confirm and toast so the campaign, not the segment, is what has already emailed a lead

* fix: stop the one-shot segment enrol from re-stamping leads that are already in the campaign, since the Leads tab's Add back runs through it and pinning a whole linked audience as hand-picked because one held-out member was restored is the accumulation this change exists to end

* fix: lock the leads a detachment is about to withdraw before deciding, because a send is reserved by stamping campaign_contact_progress and only then locking the lead row, so a reservation committing mid-pass was invisible to the delete's snapshot and could withdraw a lead whose first email had already gone; and report the already-emailed count on every toast branch, since a detach where the whole audience had been emailed changed no count and said nothing after confirming a removal

* fix: add the campaign_leads.source check constraint NOT VALID, which still enforces every insert and update while skipping a full scan of the largest table in the product under ACCESS EXCLUSIVE to learn that a one-statement-old column holds its own default everywhere
2026-09-14 22:12:15 -07:00
Matthew Meszaros e18f2efc05 feat: hold an out-of-office contact in every campaign they are a lead of instead of only the one the auto-reply was attributed to, through a new HoldLeadEverywhere applying the same per-row guard so a member's own pause and a longer running hold both survive, and leaving completed campaigns alone (issue #518) 2026-09-14 21:54:05 -07:00
Suman Jana 20a56dc41b feat: add a full-screen toggle to the unibox compose window that lifts the same composer to the centre of the screen over a dimmed backdrop, collapses back to the corner on a backdrop click or Escape before a second Escape closes the window, and document it on the unibox composing page (#503) 2026-09-14 21:14:08 -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 25e128c409 fix: stop the form builder canvas tearing apart when fields are reordered by dropping dnd-kit's rectSortingStrategy, whose scaleX/scaleY are the ratio of two mismatched field rects and stretched every sibling out of the card, for a canvas that holds still behind an insertion caret, plus a real end-of-form drop target so dragging the first field to the bottom lands it last instead of resolving to the pane-sized canvas droppable and doing nothing (issue #497) 2026-09-14 20:59:10 -07:00
Matthew Meszaros ffd27bc46d fix: stop every out-of-office notice and bounce opening a high-priority CRM follow-up by classifying machine replies from their headers and gating the task on a per-intent setting, and give the Tasks page multi-select with select-all-matching, bulk status, priority and delete over new PATCH and DELETE /crm/tasks endpoints (issue #471) 2026-09-14 12:20:47 -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