Commit Graph
657 Commits
Author SHA1 Message Date
Matthew Meszaros 89daae3f29 feat: make the send plan count a lead bound to a spent mailbox as waiting for it (leads.waiting_on_sender), charge a behaviour profile's spent budget and hourly ceiling to the plan rather than to hours or spacing, fold a foreign-timezone mailbox's 8pm close into its pacing, report the UTC budget day and keep the waterfall adding up when a cap was lowered after sends, read the pool's sends today in one query and cache the plan and workspace capacity for a few seconds, share one cold-ramp notice builder between the drawer and the plan, let the wizard estimate survive a counter miss, and stop a malformed plan payload from taking the campaign overview down 2026-09-19 09:45:03 -07:00
Matthew Meszaros 150dc7df6e feat: add a Today's sending plan to the campaign overview (GET /campaigns/:id/send-plan, derived through the scheduler's own gates: per-mailbox cap clamps, warmup graduation, health bands, other campaigns on the same mailbox, hours, spacing, window, plan allowance, new-lead cap and leads due, as a waterfall that adds up), feed the sidebar meter and the wizard estimate from the same clamps instead of summing configured caps, floor the campaign chain's next tick at the pool's spacing rather than one mailbox's whole gap, fold the compact Advisor strip to one line, add warmbly campaign plan and warmblyctl campaign plan, and document it (issue #606) 2026-09-19 09:31:46 -07:00
Matthew Meszaros 530b184748 Merge pull request #607 from warmbly/feature/two-factor-setup-flow
feat: rebuild two-factor setup in Settings > Security as a three-step wizard with a QR code, manual setup key and TOTP parameters, inline code errors, and recovery codes with download, copy and print; show enable date and remaining recovery codes, add POST /auth/2fa/recovery-codes to regenerate them, return two_fa_invalid_code on a mismatched code, and update the security guide, API reference, error codes and OpenAPI
2026-09-19 15:48:03 +00:00
Matthew Meszaros e9d1a7e734 feat: reserve the per-account reauth attempt atomically before checking a password or 2FA code, keep the 2FA dialogs open while a request is in flight, move and trap focus in them, show a retry row when 2FA status fails to load, and drop Idempotency-Key from the recovery-code route with the reason documented 2026-09-19 08:36:59 -07:00
Matthew Meszaros a1d3f3f3f1 feat: open a message details panel in the unibox from the recipient line, sender and an info icon, showing every From, Reply-To, To, Cc and Bcc address, sent and received times in the reader's zone, the mailbox, folder, size, Message-ID and In-Reply-To with one-click copy; summarise all recipients on the header line; add email_id and folder to GET /unibox/:id and document both 2026-09-19 08:22:14 -07:00
Matthew Meszaros de10ca5216 feat: put 2FA disable and recovery-code regeneration behind the per-account reauth attempt budget and document it in the account API reference 2026-09-19 08:16:53 -07:00
Matthew Meszaros 274ff888a5 feat: rebuild two-factor setup in Settings > Security as a three-step wizard with a QR code, manual setup key and TOTP parameters, inline code errors, and recovery codes with download, copy and print; show enable date and remaining recovery codes, add POST /auth/2fa/recovery-codes to regenerate them, return two_fa_invalid_code on a mismatched code, and update the security guide, API reference, error codes and OpenAPI 2026-09-19 08:15:31 -07:00
Matthew Meszaros 464ec521ca feat: present the $15 pool plan as the Warmup plan everywhere: rename the plan row (migration 000185), add it to the dashboard catalog so the header and billing overview name it, show the cloud tier in a self-hosted instance's header pill and a Plan section under Settings > Warmbly Cloud with upgrade and manage links to the cloud billing page, nudge on the mailboxes page only when the free pool is full, drop the self-host framing from the cloud's checkout dialog, paths panel and locked screen, rebuild the checkout dialog in the plan chooser's style, pitch Premium on deliverability from one shared benefit list, and update the billing and Warmbly Cloud guides and the pricing FAQ 2026-09-19 05:57:41 -07:00
Matthew Meszaros 848e64865d Merge pull request #600 from warmbly/fix/mailbox-disconnect-and-prod-errors
fix: workspace-scoped mailbox disconnect, eviction of mailboxes whose row is gone, and the production errors from this morning
2026-09-19 11:31:28 +00:00
Matthew Meszaros 834da184d9 feat: clear every dependency advisory that has an upstream fix, dropping the AWS SDK's legacy-rustls-ring default feature that was pulling a second hyper 0.14, rustls 0.21 and rustls-webpki 0.101 into the tracking service alongside the current ones, moving async-nats to 0.50 for the last old webpki and reqwest to 0.12, boxing the NATS producer variant the bigger client made oversized, refreshing the node trees with overrides for the esbuild and postcss-selector-parser that fumadocs pins, recording why the two unpatched cowlib advisories cannot be reached from a service that sets no cookie, and deciding the credential-validation timeout from the subscription context's deadline rather than the error's shape 2026-09-19 13:25:33 +02:00
Matthew Meszaros 1497762d66 feat: add live regression tests proving replies to a campaign rotating across several mailboxes stamp each lead as replied and reach the campaign reply count, with and without thread headers, and document in the analytics guide that each lead's reply is expected in the mailbox that wrote to them 2026-09-19 04:05:34 -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 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 8664684b3f Merge pull request #595 from warmbly/fix/warmup-system-issue-592
feat: hold a pending warmup send when the day's target is cut after it was scheduled
2026-09-19 06:03:47 +00:00
Matthew Meszaros d46cfad597 feat: check the warmup daily target again at the moment a send executes rather than only when the next one is placed, so a spam placement, health band or partner loss that cuts the target while a send is pending holds it as skipped_daily_limit and parks the chain at the next opening with a reply-back's aim intact, fail closed when that count cannot be read, share one target resolver between the placer and the send-time gate, add the skipped_org_suspended task status the suspended-workspace hold has written since #233 without a migration so its write stops failing and leaving the task pending for the dispatcher to re-fire, and anchor the ramp live fixture in UTC off the day boundary so its assertions no longer depend on the host timezone 2026-09-18 22:39:52 -07: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 e8f14bb2fd feat: address the review on the Gmail app-password connect by reading BOX_GOOGLE_OAUTH_CONNECT through config.GoogleOAuthConnect in the instance-settings table so a yes/on value cannot display true against a gate that parses it as false, dropping the coming-soon line from the walkthrough banner on deployments where Google sign-in is actually available, naming the 2-Step Verification app-password control an administrator still has rather than the Less secure apps page Google removed, saying the OAuth client re-authorizes existing mailboxes as well as refreshing them, and marking the marketing send trace as the Google sign-in path 2026-09-18 22:18:56 -07: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 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 bd092dcf04 Merge remote-tracking branch 'origin/main' into fix/reply-attribution-and-human-opens
# Conflicts:
#	internal/app/consumer/event_new_email.go
2026-09-18 14:44:41 +02:00
Matthew Meszaros 6aebfe7e63 feat: store IMAP-synced addresses as Name <addr> like the Gmail and Graph syncs instead of Name (addr), teach mailhdr.Bare, the reply path's sender and recipient checks and the warmup sender fallback to read the old form for existing rows and older workers, so a reply into an IONOS or any other IMAP mailbox is attributed to its lead again after the address checks added on 16 September refused every one of them, and add a consumer sweep that re-offers unclaimed inbound mail answering a campaign send or coming from a contact to reply processing at boot and daily so the replies missed that week are attributed without anyone touching the database 2026-09-18 14:37:35 +02:00
Matthew Meszaros 6a236423be Merge pull request #590 from warmbly/fix/warmup-thread-replies-out-of-inbox
Keep hand-typed replies in warmup threads out of the inbox and unibox
2026-09-18 12:33:28 +00: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 e737506ba0 feat: recognise a reply typed by hand in a warmup thread by the message it answers (In-Reply-To against warmup sends, receipts and earlier recognised turns, locally and through the pool link), keep it out of the unibox, file it out of the customer's Gmail, Outlook or IMAP inbox with the same folder action, record each recognised turn in warmup_thread_messages so the turn after it is recognised too, and let the daily sweep repair replies that already leaked 2026-09-18 14:12:33 +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 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 ae4c1631e9 Merge pull request #586 from warmbly/fix/avro-field-defaults
fix: stop a new Avro field refusing the whole envelope, file historical warmup leaks out of the customer's mailbox, and stop a rollout evacuating a worker
2026-09-18 09:35:21 +00:00
Matthew Meszaros a0a19edeae feat: register a schema document whose fixed defaults are code-point strings and whose nested nulls are null so the registered envelope parses back, keep the warmup unibox row until the filing action is on the bus and the mailbox lookup is not a transient failure, recheck the heartbeat key before evacuating a worker, match the IMAP namespace prefix case-insensitively, and state the BACKWARD direction correctly 2026-09-18 11:29:48 +02:00
Matthew Meszaros a7cabe1b95 Merge pull request #585 from tunglambk/fix/instant-branch-target-wait
fix: an instant conditional branch no longer applies its target step's wait_after (#583)
2026-09-18 09:29:22 +00:00
Matthew Meszaros 292b523c5b feat: document in the sequences guide that an instant branch's email target goes out on the next scheduling pass inside sending hours and mailbox spacing while a non-instant path keeps the target's wait 2026-09-18 02:23:55 -07:00
Matthew Meszaros bd1837833e feat: give every derived Avro field its zero as a default and register the marshalled schema so adding a field cannot refuse the whole envelope and stop every publish, file historical warmup leaks out of the customer's own mailbox rather than only the unibox, and make a worker earn a 10-minute absence before its mailboxes are evacuated so a version rollout costs no migrations 2026-09-18 10:34:36 +02:00
tunglambk cf7e530e15 feat: release a cloud-managed mirror's cloud link when it is deleted through the mailbox delete, using the delete-only revocation that never calls back into the mailbox delete, so the mailbox returns to the cloud workspace instead of staying claimed by an instance that no longer holds it (issue #582) 2026-09-18 07:17:40 +00:00
Matthew Meszaros 8240f14e8b feat: fail closed on incomplete cloud mailbox revocation and document retry-safe deletion and TLS diagnostics 2026-09-17 21:21:34 -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 68babc30f3 feat: give the mailbox delete its own cloud revocation that calls the pool before dropping the local row and refuses an unreadable link, so a nil answer is proof the credential is gone rather than proof the local row went, and drive the greylisting evidence guard through the real handler with stub repositories so removing it fails CI where the live test skips 2026-09-17 20:52:54 -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 2ad9ed633e feat: stop the warmup guide's diversity note claiming a single workspace means the reader's own, since one distinct workspace is the signal whoever it belongs to 2026-09-17 20:23:37 -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 7cca41b94d feat: document the mailbox_cloud_unenroll_failed conflict that a delete now returns when a Warmbly Cloud enrollment cannot be revoked, in the 409 section of the error codes reference and as the delete endpoint's own error table alongside mailbox_worker_unreachable 2026-09-17 20:05:11 -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 e36a070595 Merge remote-tracking branch 'origin/main' into fix/worker-capacity-account-distribution 2026-09-17 08:02:45 -07:00
Matthew Meszaros 8a60c6bab3 feat: build realtime on OTP 27 and update vulnerable Elixir dependencies 2026-09-17 07:54:34 -07:00
Matthew Meszaros 0ea5a193f2 feat: merge security dependency fixes before requeueing worker capacity changes 2026-09-17 07:26:06 -07:00
Matthew Meszaros 6f0314081c feat: update vulnerable dependencies across Go Phoenix docs site and web 2026-09-17 07:04:40 -07:00