Matthew Meszaros
eac3f6d615
Merge remote-tracking branch 'origin/main' into fix/sso-link-existing-password-account
...
# Conflicts:
# docs/content/docs/guides/security.mdx
2026-09-21 04:28:20 -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
9426c0da51
feat: validate every person, workspace and company name through internal/pkg/displayname on each write path (profile, onboarding, setup, IdP sign-in, org create and rename, org import, enterprise inquiry, admin testers, warmblyctl) with a 400 invalid_name code, render stored names in platform email through the same rules, mirror them in the web forms, check the org slug format, and document the rules in error-codes, security and AGENTS.md
2026-09-21 03:34:03 -07:00
Matthew Meszaros
0f60fd9b84
feat: attach a Google, Apple or OIDC identity to an existing password account only after that account's password is presented: resolveFederatedUser parks the sign-in as link_required with a single-use sso_link pending token, POST /auth/sso/link checks the password against the provider-asserted address on the sign-in failure budget and links then issues the session through finishLoginAs, the dashboard collects it on a new login step, and the API reference, endpoints list, security guide and OpenAPI spec describe the third login result
2026-09-21 03:25:29 -07:00
Matthew Meszaros
f11df9268f
Merge pull request #640 from warmbly/fix/admin-list-pagination
...
feat: make every admin panel list page past the first and filter by id, and report failed admin requests
2026-09-21 09:29:48 +00:00
Matthew Meszaros
cc244e5159
feat: make every admin panel list page past the first and filter by id: bind query-string ids through models.ParamUUID since gin cannot set a uuid.UUID, page the explorers and secondary lists by an opaque offset cursor with an id tiebreak instead of an id keyset that disagreed with the sort, page the audit log on (created_at, id) with an inclusive YYYY-MM-DD end day and read next_cursor on its page, cast every before-date bound to timestamptz, coalesce nullable audit ip and user agent, and report failed admin queries and 5xx mutations to PostHog or Sentry with method, path, status, code and request id
2026-09-21 02:11:38 -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
0ea00926c9
feat: apply the warmup inbound cap inside the candidate query before the tier is sized or sampled and count mail dispatched today alongside verified arrivals, judge the tampering band apart from the rate bands and keep the more severe finding, and bound received analytics by UTC instants instead of a session-timezone date cast
2026-09-20 10:15:33 -07:00
Matthew Meszaros
26594391c8
feat: judge tampering with received warmup mail on a ladder inside the health bands, one deletion warns, two pause for seven days and four or two spam flags block for thirty, instead of a review-required block on the first deletion ( #635 )
2026-09-20 09:50:42 -07:00
Matthew Meszaros
d6384d3c0e
feat: make cross-tier warmup an exchange so a proven free mailbox writes back to the paying mailboxes that wrote to it, favour the inbox owed the most on every draw, cap what any inbox receives per day inside WarmupPartnerCandidates so a thin tier is neither starved nor flooded, and surface received counts in the mailbox drawer, warmup analytics and the API ( #633 )
2026-09-20 09:42:53 -07:00
Matthew Meszaros
7b93e48bd4
feat: make Archive mean something everywhere by keeping filed conversations out of every working unibox view except All mail and the Archive folder, read a mailbox address out of the raw From header so Awaiting reply stops missing every thread sent as "Name <addr>", file and mark read by thread id rather than by message id, and add per-row triage actions plus a multi-select selection bar to the conversation list
2026-09-20 07:16:35 -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
c20d1c99f2
feat: race a 587 STARTTLS dial against a mailbox's silent port 465 on every send and connect check so the fleet keeps sending where outbound 465 is blocked, store a connect that passed that way with 587, name the port actually used in a refusal, make the Google app-password hint say Google itself refused the pair and name the alias and wrong-account causes, and render long error toasts wide, dismissable and longer-lived instead of a narrow four-second column
2026-09-20 13:16:52 +02: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
b728fff132
Merge pull request #619 from warmbly/feat/typesafe-judgments
...
feat: TypeSafe judgments across the product, with inbox tagging that acts and works out of the box
2026-09-20 07:41:39 +00: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
addb956ad6
feat: shared TypeSafe client under internal/pkg/typesafe with inbox tagging phases 2 and 3 (hold, stop, task, suppress behind workspace switches, reversible ones on by default), labels seeded at workspace creation and by the follow-up sweep, premade inbox views (hot leads, needs a reply, follow up, declined, automated), typed reply classification and a reply_intent branch condition, an inbox agent draft gate, Advisor copy judgment with a cached editor re-check, warmup content lint, bounce cause classification that keeps a blocked address sendable, and per-form submission triage
2026-09-19 23:33:37 -07:00
Matthew Meszaros
dee54417a3
Merge pull request #612 from warmbly/feature/campaign-daily-send-view
...
feat: add a Today's sending plan to the campaign overview and feed the sidebar meter and wizard estimate from the scheduler's own clamps (issue #606 )
2026-09-19 17:04:19 +00:00
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
ed50c278fa
feat: make view-preference writes partial so a sort click before the layout loads keeps the saved columns (PUT /me/views/:view coalesces on the bound parameter and returns the row in one query, with a live test), validate column ids against each view's known columns and sorts against the contacts search's, reject a malformed custom sort on the bulk select-all and export paths too, key the browser's layout cache by user as well as workspace, commit a drag reorder once on drop instead of once per crossed row, save a Name-only layout as ["name"] so it cannot read as the default, start text sorts ascending from the Sort menu as a header click does, and share the pickers' checkbox square as a ui primitive
2026-09-19 09:38:59 -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
62201a2dd3
feat: let each member choose, reorder and persist the contact list's columns (custom fields included) and sort on any of them: a user_view_preferences table (migration 000187) behind GET/PUT/DELETE /v1/me/views/:view, a column registry that renders the contacts and campaign Leads tables from a saved layout, a Columns chooser with drag reorder and a Sort menu on both toolbars, click-to-sort headers, sort_by custom:<key> plus company and phone sorts in POST /contacts/search resolved once for Search and SearchIDs with a nullable keyset cursor, and the contacts guide, API reference, openapi, error codes and export-import docs updated
2026-09-19 09:24:17 -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
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
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
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
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
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
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
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
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
a900f1e04c
feat: make worker moves atomic and preserve safe concentration and health state
2026-09-17 07:45:23 -07:00
Matthew Meszaros
2a1e55354d
feat: merge latest main before requeueing worker capacity fixes
...
# Conflicts:
# internal/app/stripe/service_test.go
2026-09-17 06:57:20 -07:00
Matthew Meszaros
c1e45b194a
feat: merge latest main before worker capacity queueing
2026-09-17 06:26:07 -07:00
Matthew Meszaros
8c7827c199
feat: make Stripe credit auto-top-ups idempotent across retries
2026-09-17 15:25:35 +02:00
Matthew Meszaros
d6025ea4c0
feat: address worker capacity review findings with safe migrations and recovery reporting
2026-09-17 06:24:14 -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
d1aa3d4361
feat: classify ambiguous Outlook and Apple image-proxy opens by delivery timing and stop inventing recipient device metadata
2026-09-17 04:27:34 -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
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
Matthew Meszaros
0f5ca71155
feat: correct mailbox sending metrics and workspace analytics across dashboard surfaces
2026-09-16 21:44:42 -07:00
Matthew Meszaros
2255e2145d
feat: reject outbound mailbox copies and mismatched campaign threads before they can mark contacts replied for stop-on-reply (issue #549 )
2026-09-16 08:24:19 -07:00
Matthew Meszaros
31c1f101c2
feat: give EmailSentEvent and WarmupEmailSentEvent their own derived Avro schemas so the email-events and warmup-events analytics streams stop failing at serialize on every send and finally register a subject ( #546 )
2026-09-16 04:20:15 -07:00
Matthew Meszaros
140c7de436
feat: add the admin panel's Promo codes page and route the six /admin/discounts endpoints that existed as handlers but were never wired, so a launch offer is built in the operator UI instead of an INSERT against production, with caps that an explicit null can actually clear on PATCH
2026-09-16 04:04:09 -07:00
Matthew Meszaros
746dd40469
feat: strengthen the Avro round-trip tests after a cutover they failed to catch ( #536 )
...
* feat: compare the decoded event body's fields and not only its type, fill arrays so every uuid carries a real value instead of the zero one a codec could drop unnoticed, and decode once in a process that has never encoded, because production is four processes and one of them only ever reads what another wrote
* feat: register the union body types at package load instead of on first schema build, which is what a process that only ever decodes never reached, so every worker command arrived as a map keyed by its branch name, went through the JSON fallback, and became a struct with every field zero and no error anywhere
2026-09-15 20:25:21 -07:00
Matthew Meszaros
f106c8541d
feat: make the bus envelopes Avro-encodable ( #535 )
...
* feat: make both bus envelopes Avro-encodable by deriving each one's schema from a declared registry of body types, with a union branch per body and our own struct walk that skips unexported fields and honours avro:"-" before descending, so the schema describes exactly what encoding/json already puts on the wire, and narrow the two sync cursors on the wire DTOs to int64 because Avro has no unsigned 64-bit type
* feat: stop the instance health check, the config registry and the docs all claiming Avro cannot serialize a worker envelope, which stopped being true once the envelopes carried a declared union, and check the one thing that is still a real misconfiguration instead: avro selected with no SCHEMA_REGISTRY_URL to resolve against
* feat: emit a reference the second time a record appears in an envelope schema instead of defining it again, because Avro names a record once and a document that defines warmbly.events.Token three times is rejected outright, and keep the Schema Registry round-trip as a skip-by-default test since only a registry judges the document rather than the objects it was built from
* feat: carry uint64 as Avro fixed(8) rather than long, which lets the sync cursors keep their unsigned type instead of being narrowed, name every event field after its json tag so the schema and the JSON wire agree, and populate every field in the round-trip test because zero values are why a uint64 mapped to long passed in the first place
* feat: frame Avro in Confluent's wire format and encode through hamba's default API instead of going through avrov2, whose private avro.API holds a type resolver avro.Register cannot reach, so a union body failed there with unable to resolve type while encoding cleanly against the same schema, and keep the registry round-trip as a skip-by-default test
2026-09-15 10:50:30 -07:00