Commit Graph
903 Commits
Author SHA1 Message Date
Matthew Meszaros 19eb68ecd5 feat: re-read stored opens and clicks by the live origin rules in batched, resumable consumer passes under an advisory lock instead of a boot-time SQL backfill, never read a proxy string on a click as Apple Mail or Gmail, keep recognising Outlook, Proton Mail, Yahoo Mail and HEY by name, show the mail app behind a click in the expanded timeline row, and note that other iOS mail apps count as Apple Mail 2026-09-22 22:31:27 -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 7813d77efc feat: send nothing to TypeSafe when an import's header row holds an address, date or number, skip placeholder Column N headers, accept only the options each column was offered, never infer Subscribed, Categories or Email, prefer an exact existing field name before a folded match on the server as the client does, compute value kinds once per preview, and make Enter in the empty field search a no-op 2026-09-22 21:29:22 -07:00
Matthew Meszaros 25652476ba feat: store the timezone a new workspace is created with, return a mailbox's own and workspace timezone from its PATCH, pin following campaigns and mailboxes to the workspace zone on the 000198 down migration, page through every campaign and mailbox before the Timezones summaries and Set all, let the campaign wizard and onboarding pick up a workspace zone that loads late, and qualify the workspace timezone copy to mailboxes that follow it 2026-09-22 20:38:18 -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 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 1520da2fa8 feat: look up moved mail against each skipped folder with one SELECT per folder and cap the reconciliation by searches rather than rows, never settle a row whose lookup failed, refuse a skip name that is a saved folder the matcher keeps and purge only what it will stop following, drop the map entries of parked arrivals too, ignore UIDVALIDITY 0 when matching a rename into the skipped subtree, carry the listing memo and pending mark across a rename, ask before ticking a folder in the drawer since imported mail is removed, share one inbox-deleted publisher between the consumer's removal paths, and say in the CLI help that an emptied list does not restore removed mail 2026-09-22 05:45:38 -07:00
Matthew Meszaros 9a7ef088cf Merge remote-tracking branch 'origin/main' into feature/imap-folder-exclusion 2026-09-22 05:17:37 -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 62ea7ef906 feat: gate the dashboard version pill's update actions on a session that presented a second factor, carry session_mfa_verified on the web User model, pass the API error code into the permission-denied event and give admin_mfa_required its own two-factor dialog variant linking to Settings > Security instead of the Roles & access advice, and document the rule on the updates page 2026-09-22 03:42:56 -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 ff2204f8ef feat: route the sso_link completion through the one login path (completeLogin) so the ban check, the 2FA gate, login recording and device memory apply before a parked identity is attached, carry the session provider into every 2FA challenge so a Google or Apple sign-in on a 2FA account is recorded as such, make IdentityRepository.Link report a pair another account holds as ErrIdentityTaken instead of silently updating nothing, refuse a spent address budget before charging a link try, drop the dead expiry check and the duplicate link fields on the web Session model 2026-09-21 03:57:29 -07:00
Matthew Meszaros 4578980b34 feat: fall back to My Organization when the first name is blank in displayname.DefaultWorkspace, and only treat a scheme as a link when a non-space follows its colon so names like Big Data: EU pass in both the Go and web validators 2026-09-21 03:42:17 -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 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 eaa7cfb129 feat: mount the global modals inside UpgradeDialogProvider so the mailbox-allowance dialog can offer the plan that lifts the cap instead of throwing UpgradeDialogProvider not found, and assert the provider boundary so a sibling cannot drift back outside it 2026-09-20 20:08:24 +02: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 5b16a09b02 feat: write public objects without a canned ACL so a bucket whose ownership is owner-enforced still stores avatars, form assets, OAuth logos and email-body images, give the passkey login challenge its own per-IP budget separate from the one password sign-in draws on, and surface the API's own message at upload and passkey call sites instead of a generic sentence 2026-09-20 15:40:52 +02: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 1342c47fc6 Merge pull request #622 from warmbly/fix/autherror-posthog-sourcemaps
feat: stop a dashboard tab whose token went away from polling forever
2026-09-20 09:23:31 +00:00
Matthew Meszaros 0cac62b4f4 feat: stop a dashboard tab whose token went away in another tab from polling forever, by announcing the ended session from the api client's no-token path and by treating the auth token key disappearing from storage as the session ending in every other tab, so the app redirects to sign-in on the first failed poll instead of filing one unhandled AuthError a minute for the life of the tab 2026-09-20 11:20:38 +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 d1a4add567 feat: add the new CHECK constraints NOT VALID and validate them in migration 000189 so campaign_leads is not scanned under a write lock, keep a step's copy-judgment findings open when the Advisor's judge cap or an outage left it unjudged, validate every TypeSafe answer's type and bounds and version the copy-judgment cache key, mirror the reads-as-bulk rule in the editor's all-clear, and refuse inbox classification without a configured client 2026-09-19 23:59:19 -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 d7d6ecfbbf feat: stop a failing API from signing people out and then spinning forever, by ending a session only when the server actually refuses the refresh token rather than on any 500 or timeout, announcing that end so the app redirects to sign-in once, and standing the websocket's reconnect loop down when there is no token left for it to use 2026-09-19 19:53:54 +02: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 d667c1dacd feat: keep the send plan honest at the edges: a behaviour mailbox reopening after the window closes is closed for the day, only a mailbox coming back on its own (spent, closed, spaced out) holds its bound leads as waiting, the min gap is charged from when the mailbox is next open, an end date later today ends the day there, explainCap is stagedCap's last stage so there is one clamp chain, last-send times and warmup health are read for the pool in one query each, the plan cache is keyed on the campaign's status and updated_at so a start or an edit is answered fresh, and the workspace posture row links to /app/deliverability 2026-09-19 09:58:52 -07:00
Matthew Meszaros c73b30c112 feat: make the column chooser's reorder grip a focusable button that moves a column with the arrow keys, count a saved sort alone as a customised view so Reset to default stays available, and list unknown_view under the 404 codes rather than the 400 table 2026-09-19 09:46:08 -07: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 d8a3e70450 Merge pull request #609 from warmbly/fix/suppression-add-dialog
feat: suppression add dialog, row menu fix, and warmup suppression cleanup
2026-09-19 15:51:20 +00: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 6b834b49f6 feat: cap the suppression add dialog's chip preview at 200 with a count of the rest, and refuse a paste over the server's 5000-entry limit before submitting instead of after 2026-09-19 08:46:24 -07:00
Matthew Meszaros 68ec5a016e feat: delete the reauth attempt counter when a refund leaves it at zero or below so it always carries an expiry, and focus the first field of a 2FA dialog on open 2026-09-19 08:43:11 -07: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 b8f47c94f4 Merge pull request #608 from warmbly/feat/unibox-message-details
feat: open a message details panel in the unibox from the recipient line, sender and an info icon, with every address, exact times, mailbox, folder, size and identifiers, and add email_id and folder to GET /unibox/:id
2026-09-19 15:31:23 +00:00
Matthew Meszaros 69ddac8c75 feat: replace the inline suppression add form with a modal dialog that previews each pasted address and domain as chips with counts and flags unrecognized values, fix the suppression row menu so Remove from list sits on one line with its icon and the added date no longer wraps, tint danger menu item icons red, and add migration 000186 removing suppression entries and deliverability events that only warmup sends produced 2026-09-19 08:23:10 -07:00