Commit Graph
185 Commits
Author SHA1 Message Date
Matthew Meszaros 61d16dfe9a feat: add Warmbly for Slack with the AI assistant in DMs, mentions and the assistant pane, unified inbox threads with reply, AI draft and lead actions, per-category notification routing and DMs, account linking, dashboard Slack settings, manifest and docs 2026-10-03 15:35:11 +02:00
Claude 80594bb120 feat: classify inbound bounce/complaint technical lookup failures as retryable and propagate them from the consumer handlers instead of dropping them as no-match 2026-10-03 06:37:35 +00:00
Matthew Meszaros 1aeb45773b feat: check threads changed since each workspace's last follow-up pass by when the message was stored (new unibox_emails.ingested_at) or its verdict written, behind a persisted watermark, give each workspace's sweep a single leased walker whose saves are conditional on the lease, stop the cursor at the last thread whose label was written and step past a page or thread after three consecutive failures, keep the cycle running when the changed-thread check fails, and accept warmblyctl inbox-tag follow-ups --limit as a deprecated no-op 2026-10-01 22:52:43 -07:00
Matthew Meszaros 6758bc8035 feat: page the hourly inbox follow-up sweep through each mailbox newest first in bounded keyset pages that resolve thread state only for the threads on the page, resume each workspace's cycle from a cursor persisted in inbox_follow_up_sweeps under a per-pass time budget, check recently active threads first every pass, and add the unibox_emails (email_id, internal_date, id) index the walk reads 2026-10-01 22:29:41 -07:00
Matthew Meszaros 4bfebe8538 feat: write a relayed folder answer straight to the unibox row without reading it first, since the unibox GetByID marks an unread message seen and archiving unread mail must leave it unread, with a consumer test that fails on any read 2026-09-30 05:11:17 -07:00
Matthew Meszaros b2d6d100dd feat: relay unibox Archive, Delete and Move to inbox to the mailbox itself through a new MESSAGE_FOLDER worker command (Gmail label batchModify, Outlook well-known folder moves with the message map re-keyed around each Graph id change, IMAP MOVE after locating each message by Message-ID), answered by relayed UPDATE_FOLDER events that write only provider_folder and the moved handles, relaying every row a filing moved so an Undo right after Archive still reaches the mailbox, with a per-mailbox Mirror Archive and Delete switch (email_accounts.relay_folder_moves, migration 000235, on by default), warmup receipts, drafts and non-Gmail sent copies left in place, and the unibox, mailboxes, API, events and OpenAPI docs updated 2026-09-30 04:41:03 -07:00
Matthew Meszaros 0ce8a6ee79 feat: claim each held warmup spam move with a lease, fix its verdict before applying any effect and complete it only after every sender and recipient write succeeds, so consumer replicas never charge a strike that disagrees with the recorded verdict and a failed write re-applies the same verdict 2026-09-30 07:08:40 +02:00
Matthew Meszaros 57a26c4ee6 feat: attribute a received warmup email moved to spam on the evidence around it before charging anyone, never for mail that arrived in spam (warmup_received.landed_spam), holding each move 30 minutes in warmup_spam_moves and deciding provider on a cross-workspace correlation or a move straight after arrival with nobody there, owner on provider-reported read, unread or star activity in mailbox_owner_activity or a repeated uncorrelated pattern in a used mailbox, nobody otherwise, weighting a spam move as one strike, withdrawing owner verdicts a later correlation explains, clearing the strikes behind a hold an admin lifts or an appeal approves, migration 000233, docs and guide 2026-09-30 06:52:32 +02:00
Matthew Meszaros e76cdc6f3a Merge remote-tracking branch 'origin/main' into fix/recent-posthog-errors-2 2026-09-29 20:13:29 +02:00
Matthew Meszaros d162f0aaf6 Merge pull request #748 from warmbly/fix/gmail-archive-unibox-inbox-sync
feat: file Gmail mail where Gmail moves it (archive, delete, spam, back to inbox) via an UPDATE_FOLDER event resolved against provider_folder, repair rows already stranded in the unibox Inbox with a six-hourly Gmail folder reconciliation, and close the open unibox conversation on a scope change with a close button in the reader header
2026-09-29 17:57:41 +00:00
Matthew Meszaros ca66b25d8d feat: register the release's bus schemas from the booting backend and hold the fleet on its current release until the backend runs the new one and the schema registry accepts them, check every published schema against a recorded snapshot in CI (make schemas) so a change the registry would refuse fails before merge, pin a FORWARD or FULL registry subject to BACKWARD on refusal, bound advisor narration so the run still lands, store unlabelled 8-bit mail with its invalid UTF-8 replaced, treat a pending or credential-less passkey ceremony as a cancellation, and drop link-scanner rejections from site error tracking 2026-09-29 19:20:07 +02:00
Matthew Meszaros 5fdb2e40a5 feat: keep a refused lead copy from spending the lead's send attempts, drop campaign-wide CC and BCC addresses that bounced on the campaign, resolve copies only for email steps, fail a reply or link unsubscribe when the lead's copies cannot be read instead of guessing, credit a copy's message to a lead only when it replies in a thread, show each copy's status in the Leads CC badge, and close the CC picker on Escape from any focused child 2026-09-29 10:11:17 -07:00
Matthew Meszaros 0ed98a8c55 feat: copy up to two colleagues on every email a campaign sends one lead (campaign_lead_cc, migrations 000230-000231) with a drawer CC editor that suggests same-company contacts, hold a copied contact's own lead so nobody gets two threads, count a copy's reply as the lead's, opt out every copy on a link unsubscribe, drop a bounced or refused copy without bouncing the lead, skip suppressed campaign CC and BCC addresses, and document the endpoints, CLI, skills and OpenAPI 2026-09-29 09:53:27 -07:00
Matthew Meszaros e2f319cfd8 feat: file Gmail mail where Gmail moves it (archive, delete, spam, back to inbox) via an UPDATE_FOLDER event resolved against provider_folder, repair rows already stranded in the unibox Inbox with a six-hourly Gmail folder reconciliation, and close the open unibox conversation on a scope change with a close button in the reader header 2026-09-29 09:43:13 -07:00
Matthew Meszaros ad4104c57a feat: never add a warmup strike from a recheck (confirm it, or withdraw it when the message is still in the mailbox or retention removed it since), revise a tampering hold before its strike is deleted so a retry completes it, compare-and-swap the revision on the hold's term, scope the ledger revision to the whole address, keep tampering events 37 days so a live hold's strikes survive pruning, answer an inconclusive search as unknown so IMAP rechecks stop, and return an IMAP transport failure as an error 2026-09-29 05:12:36 -07:00
Matthew Meszaros e5eb3b0ff1 feat: re-decide a withdrawn warmup tampering hold on the strikes left in the seven days before it was imposed (on the pool row, or the reputation ledger for a mailbox out of every pool), stamp an old strike verified only when a worker answers its search and ask again after six hours, retry a removal check for a mailbox still loading, redeliver a failed strike, search IMAP folders in one session hold, and share the Message-ID search helpers 2026-09-29 04:57:45 -07:00
Matthew Meszaros c5a981d86c feat: record a warmup deletion strike only after the worker searches the mailbox and finds the message in the trash or gone, so a move reported as a removal (Graph, a provider filter, a mailbox rule, a second Warmbly instance, our own filing) is never charged, withdraw strikes whose message is still in the mailbox and lift the pause or block they imposed, recheck deletion strikes recorded before the search once via warmup_tampering_events.verified_at (000229), and clarify the tampering reasons and warmup guide 2026-09-29 04:28:34 -07:00
Matthew Meszaros 53327384b7 feat: carry review-required cloud blocks and the later end of an equal cloud hold into the local pool, keep a cloud hold through a failed unenroll, leave standing changes to the sync so each fires its webhook, skip unchanged standings, read risk bands through the standing-aware health read, report failed standing syncs on the job panel, and document the source field in openapi.json 2026-09-28 09:05:11 -07:00
Matthew Meszaros a749a8e353 feat: hold Warmbly Cloud's warmup standing on a linked self-hosted instance, syncing each enrolled mailbox's health into cloud_link_mailboxes so campaign gates, send plans, lifecycle, risk pacing, account health and the Advisor enforce a cloud quarantine, block or throttle, fire the usual health webhooks and realtime events, and carry a hold into the local pool on unenroll 2026-09-28 08:46:40 -07:00
Matthew Meszaros c4f0ffffc2 feat: keep a throttled warmup mailbox in the pool by stamping blocked_at only for a quarantine or block in UpdateParticipantHealth, clear it from throttled rows in migration 000222 so partner selection and the admin pool counts stop treating a throttle as a block, and run the warmup health sweep on consumer boot so a release that changes the rules re-judges every mailbox at once 2026-09-27 10:09:01 +02:00
Matthew Meszaros 9d0f60801c feat: file an offline notification unasked when the action check fails, bring a reopened verdict's message back to the inbox, skip empty notices and require the built-in check on --recheck-notifications, keep workspace labels saved before a built-in took the name, keep the sender's subject out of the action-required notification and send it off the ingest path, and default omitted outreach settings fields on PATCH 2026-09-26 21:36:11 -07:00
Matthew Meszaros 90f3f57eb9 feat: keep automated notifications that need the recipient's action (failed payment, suspended account, suspicious sign-in, sending limit, expiring service) in the inbox with an Action required label, view and mail-that-needs-action notification, let workspace tagging questions also run on automated notifications, add warmblyctl inbox-tag backfill --recheck-notifications, and document it 2026-09-26 21:28:06 -07:00
Matthew Meszaros 014ba06b3a feat: weight warmup partners by the sender's verified spam rate per recipient mail host instead of per address domain, report deliverability warmup placement and the placement_in_spam webhook by mail host, mark IMAP warmup mail not-junk before it leaves Junk, send single-part text mail over SMTP with a fixed header order, and update the warmup, deliverability, analytics, webhook and OpenAPI docs 2026-09-26 06:30:22 -07:00
Matthew Meszaros 7f324a38ac feat: open inbox placement tests to workspaces with probes rendered like the campaign send and paced as placement tasks, Message-ID matching instead of a subject token and warmup header, instance, workspace and Warmbly Cloud seed panels, a tracking comparison, scheduled campaign monitors with alerts and optional auto-pause, monthly allowances, realtime updates and org transfer registration 2026-09-25 20:48:15 -07:00
Matthew Meszaros 6a13fd5238 feat: report the Message-ID Gmail stamps on a send and resolve a reply's campaign through the Gmail thread and In-Reply-To so campaign replies stop tagging as sales pitches (with a warmblyctl --recheck-cold-inbound backfill that clears the stale labels and a tasks thread index), let a workspace add its own yes/no and pick-one tagging questions with plain-word labels and hold/stop/task actions, and add tagging languages (44 named to the classifier; for 27, only once chosen, tagging also cuts their quoted history and reads their away and non-delivery subjects), documented in the inbox tagging guide and the OpenAPI schema 2026-09-24 21:08:35 -07:00
Matthew Meszaros 256010725b feat: count only email steps in every sent total (contact drawer, Leads view and statuses, campaign progress, guardrails, org conduct, verification evidence, segments, admin, advisor), keep line breaks in synced body text and strip quoted history in any shape, classify delivery failures before out-of-office and tag them as bounces, record statusless failure notices from X-Failed-Recipients as permanent bounces, limit reply and inbox-tag opt-outs to people answering our outreach (never bounces, auto-replies or list mail), and recheck earlier reply opt-outs, lifting with an audit entry only those the message that wrote them no longer supports 2026-09-24 09:44:17 -07:00
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 33b30272e0 feat: end a worker's new-arrival, folder-listing, sync-state, Gmail history and Graph delta event whose write is refused because its mailbox row is gone, confirmed against email_accounts rather than inferred from the foreign key, and evict that mailbox from every live worker instead of reporting the refusal or discarding it, so a worker still holding a deleted mailbox is told to stop on its next relay 2026-09-23 19:17:10 +02:00
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 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 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 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 ee9f5bf84e feat: make every skipped-folder purge drop the message map entries and parked arrivals with the rows so a message can be imported again if it moves back, persist each folder's delimiter (migration 000197) so the backend purge matches the same subfolders and case the worker skips, fail the mailbox load closed when the skip list cannot be read, mark a folder renamed into the skipped subtree as skipped, never remove a row re-fetched this pass, cap the reconciliation at 50 searches per pass and continue next pass, publish one EMAIL_DELETED after a folder purge, and resolve the account once for GET /emails/:id/sync 2026-09-22 05:33:56 -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 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 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 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 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 f9db4f435f feat: actually deactivate a mailbox whose credentials have died, by writing its status against the mailbox id instead of through the organization-scoped update that was being handed the owner's user id and so matched no row, and stop labelling a mail error's own uuid as an email account in the message 2026-09-19 20:23:17 +02: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 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 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 ffe3159256 feat: order the repair sweep's fake inbox by id so the cursor assertion is deterministic like the query it stands in for 2026-09-18 14:51:18 +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 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 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 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