Commit Graph
92 Commits
Author SHA1 Message Date
Matthew Meszaros b196b3c8be feat: count warmup placements exactly once through a consumer sweep that seeds history in batches instead of a locking migration backfill, add a concurrent unplaced-receipt index, publish WARMUP_PLACEMENT only when a receipt is counted, share one spam-flag list across warmup, placement tests and unibox folders, name ranked mailboxes from their own rows, default from to 30 days before to, keep ScrollStrip scrolling to itself, refresh account statuses at most every 5 minutes, and document rescued as requested 2026-09-24 05:28:51 -07:00
Matthew Meszaros b2d54fc873 feat: record where every warmup email lands (inbox, Gmail tab, spam) per sender, day and recipient host in warmup_placement_daily, serve it from GET /analytics/warmup/placement, cap mailbox health at the measured 7-day inbox rate, and show it as an Inbox column, a mailbox Deliverability tab and a workspace placement section; replace every visible native checkbox in the dashboard with a themed Checkbox and make the mailbox drawer's tab bar scroll 2026-09-24 05:28:51 -07:00
Matthew Meszaros ee70b7697a Merge remote-tracking branch 'origin/main' into fix/unibox-forward-original-message
# Conflicts:
#	docs/content/docs/api/reference/unibox.mdx
#	docs/content/docs/guides/unibox.mdx
#	docs/public/openapi.json
#	web/src/components/app/unibox/ReplyComposer.tsx
#	web/src/components/app/unibox/replyComposerDraft.test.tsx
2026-09-23 21:48:54 -07:00
Matthew Meszaros a5dfe169e4 Merge remote-tracking branch 'origin/main' into fix/unibox-forward-original-message 2026-09-23 21:38:59 -07:00
Matthew Meszaros bbd7942313 feat: unibox forward requires READ_UNIBOX alongside WRITE_UNIBOX, restores click tickets in a forwarded Warmbly send to their destinations, fills an empty-placeholder HTML note from its text, previews an empty-note forward in the Scheduled view, keeps a reply's leading blank lines, shares the stored-body read with GET /unibox/:id, and shows the Reply/Forward bar when a forward's message is no longer in the thread 2026-09-23 21:38:59 -07:00
Matthew Meszaros 408f1fea94 Merge pull request #671 from warmbly/feat/vendor-connect-fields
feat: connect inbox vendors with only an API key and pick mailboxes by workspace
2026-09-24 04:35:42 +00:00
Matthew Meszaros 462df7d025 Merge pull request #673 from warmbly/feat/unibox-reply-sender-picker
feat: let a Unibox reply or forward choose its sending mailbox, keep the provider thread only for the mailbox holding it, and scope scheduled sends to the organization (#670)
2026-09-24 04:34:30 +00:00
Matthew Meszaros a544847fcb feat: resolve a Unibox reply's Gmail thread per sending mailbox (its own copy of the conversation, or the thread its earlier reply started, and none for other providers or on a failed lookup keeps the handle), honour an API key's mailbox allowlist on scheduled list and cancel, fall back from a saved mailbox that is gone and block Send until the sender can send, fetch From candidates only when the picker opens, and fix the Unibox threading and drafts docs 2026-09-23 21:29:24 -07:00
Matthew Meszaros 6c4931c28a feat: unibox forward carries the original message, attached server-side from a new forward_message_id on POST /unibox/reply and stored on the queued task (migration 000208) so it goes out under the note and signature with its From, Date, Subject, To and Cc lines, sanitized HTML and text part; the composer allows an empty note and previews the forwarded message (#668) 2026-09-23 21:22:17 -07:00
Matthew Meszaros ce5eb4fd25 feat: let a Unibox reply or forward choose its sending mailbox in From (the shared MailboxPicker without Auto, kept in the draft and the undo-send), send the provider thread handle only from a mailbox that holds the thread so a switched reply threads on In-Reply-To alone, and scope scheduled sends, their cancel, count and cap to the organization whose mailboxes send them (#670) 2026-09-23 21:18:20 -07:00
Matthew Meszaros 39bb5567dc feat: order campaign Step performance by the canvas's step order (position, then created_at) instead of created_at, which the wizard writes identically for every step, number each row as its place among the email steps so it matches the canvas's Email N, label unnamed steps Email N in the dashboard, and document the steps order and position in the analytics API reference, guide and OpenAPI schema (#669) 2026-09-23 21:12:33 -07:00
Matthew Meszaros e35baa7f58 feat: connect every inbox vendor with only its API key by discovering the workspaces and organizations the key reaches (InboxKit, Zapmail, Infraforge, ScaledMail), carry the workspace in vendor mailbox and domain ids with a lookup for ids stored before, skip a workspace the key may not read, refuse a key that reaches none with mailbox_vendor_no_workspace, and add a workspace switcher to the mailbox picker that filters, counts picks and selects a whole workspace 2026-09-23 21:12:09 -07: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 1c734411a3 feat: keep warmup out of Unibox notifications and the unread badge: tie reply notifications to their unibox message (notifications.unibox_email_id, migrations 000202-000204) so they are removed with it and read when it is read, link them to the conversation, count the badge as unread Inbox conversations without snoozed ones and refetch it instead of incrementing, hold a live arrival from the sender of an unconfirmed warmup send until its id is known, and never send warmup without a token (#659) 2026-09-23 07:30:09 -07:00
Matthew Meszaros 331aeb4db3 Merge origin/main into feature/open-tracking-device-client 2026-09-23 02:06:04 -07:00
Matthew Meszaros f632a2fe6f feat: document that a click carries client_type app when a mail app made the request itself, in the timeline origin, the campaign surfaces breakdown and the OpenAPI schema 2026-09-22 22:39:59 -07:00
Matthew Meszaros 3cf743a0cd Merge remote-tracking branch 'origin/main' into fix/millionverifier-reverification-source 2026-09-22 22:17:04 -07:00
Matthew Meszaros 72272cfbe7 feat: queue a contact re-verify ahead of the backlog whatever its evidence or manual verdict while the current verdict stands, count MillionVerifier renewing subscription credits, let a paid verifier's fresh answer outrank mail older than 30 days, keep what each check said, and show Verified with MillionVerifier plus a live Re-verify button on the contact Deliverability card 2026-09-22 22:17:04 -07:00
Matthew Meszaros eca5416a6f Merge origin/main into feature/open-tracking-device-client 2026-09-22 22:16:51 -07:00
Matthew Meszaros d44fd38d90 feat: show the device and mail client behind every open and click (iPhone · Apple Mail app, Windows PC · Outlook app, Gmail · device hidden) from a new mailclient detector that recognises Gmail, Yahoo, Apple MPP, HEY, Fastmail and Seznam image proxies instead of reporting their fake browser and data-centre location, record app vs webmail and device_hidden on the open and click logs, backfill stored opens by the same rules, let the logs accept the scanner reason, and surface it on the contact timeline, a new How they read overview, the live campaign feed, recent activity, the campaign Device breakdown (surfaces), webhooks and realtime 2026-09-22 22:16:51 -07:00
Matthew Meszaros c75b3b3b0d Merge pull request #651 from warmbly/feature/import-existing-custom-fields
feat: map import columns onto the workspace's existing custom fields
2026-09-23 04:45:33 +00: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 0e97ccc88d feat: make every warmbly CLI command send the body its endpoint binds (contact create, mailbox send and set-tracking, form set-domain, campaign test, task due dates, advisor snooze, warmup appeal, integration push, oauth-app scopes, corrected inbox and suppression examples, automations and webhook edit documented as full writes), pinned by a test that decodes each body strictly into the server struct, and let POST /v1/campaigns/:id/steps take the PATCH fields as an optional body so add-step no longer creates a blank step 2026-09-22 20:26:34 -07:00
Matthew Meszaros c1acded32c feat: place import columns no header matched with one TypeSafe Jev choice call per preview (headers, value kinds and field names only, never a cell; 0.70 confidence floor, one column per field, 4s fallback to the deterministic mapping), map a column of addresses to Email by its values, report inferred_columns on both import previews, mark them in the mapper, and document what is sent in data control 2026-09-22 20:22:40 -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 38f8382cf8 Merge remote-tracking branch 'origin/main' into feature/sending-window-timezone 2026-09-22 20:13:55 -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 2bdbfe5f68 feat: list the workspace's existing custom fields in the contact import and Google Sheets column mapper (searchable, with inline create), auto-map headers to existing fields ignoring case and separators in one shared server-side suggester, flag new fields, near-duplicates and columns sharing a field, and document the matching 2026-09-22 20:07:57 -07:00
Matthew Meszaros 5a967cc3ce feat: let an IMAP mailbox exclude folders from sync (email_accounts.sync_skip_folders, migration 000196) so a folder another tool fills never reaches the unified inbox: the worker drops skipped folders and their subfolders before the walk, retires an already-synced one with its stored mail, and removes mail that later moves into one only when its Message-ID is found there; PUT /emails/:id/sync and the drawer's Sync card set the list, GET reports it with the server's folder list, warmbly mailbox skip-folders mirrors it, with docs, OpenAPI and error-code invalid_sync_folder 2026-09-22 05:15:49 -07:00
Matthew Meszaros e1989f9116 Merge remote-tracking branch 'origin/main' into fix/password-change-session-revocation
# Conflicts:
#	internal/app/auth/reset_password.go
2026-09-21 04:56:50 -07:00
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 db0f0c6132 feat: carry the caller's session into ReissueSession from the request instead of looking it up, evict every revoked session from the cache and write a revoked tombstone where the delete is refused, and answer a password change whose reissue failed with the distinct 409 password_changed_sign_in_again that the dashboard turns into a sign-out, documented in error-codes, the endpoint reference and OpenAPI 2026-09-21 04:23:46 -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 7626aaefe4 feat: end every session on a password change, the calling one included, and answer POST /auth/me/password with the token pair of a fresh session for that device; the dashboard stores the new pair, and the endpoint reference, security guide and OpenAPI document the response 2026-09-21 03:18:27 -07:00
Matthew Meszaros 3ea6118a27 feat: redeliver a warmup retention delete when the mailbox is not loaded on the worker, drop the stored body when the IMAP message is already gone on a redelivery while returning a search failure rather than treating it as absence, and encode the accepted warmup_retention_days range (0, or 3 to 3650) in both OpenAPI schemas 2026-09-21 01:21:28 -07:00
Matthew Meszaros 0a5e7947b4 feat: return a failed warmup retention delete from the worker so the bus redelivers it up to five times, re-key a Graph message in the map on every move so the sender copy's body can be dropped, never expunge a whole IMAP folder for one message (UID EXPUNGE, else MOVE to Trash, else refuse), build the two retention indexes concurrently in their own migrations 000193 and 000194, keep the dashboard stepper off 1 and 2 days, and describe retention as applying wherever the placement files warmup mail 2026-09-21 01:11:22 -07:00
Matthew Meszaros 1513419a2a feat: delete warmup mail from each mailbox once past a per-mailbox retention window (email_accounts.warmup_retention_days, else retention.warmup_mail_days, default 30) via a consumer sweep that retires the receipt and sender copy and a worker delete action that trashes on Gmail, deletes on Graph, expunges on IMAP and drops the stored body, prune per-message warmup records after retention.warmup_event_days, and count a warmup deletion as tampering only within 24 hours of arrival and never for a retired message, judging Gmail's Trash label on the same rule 2026-09-21 00:52:02 -07:00
Matthew Meszaros 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 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 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 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 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