Matthew Meszaros
|
55307b0ad9
|
feat: fix the segment dialog UI and make a contact created inside a segment join it (issue #285): the add-to-campaign picker now shows a humanised, colour-toned campaign status instead of the raw PAUSED_NO_ACCOUNTS enum, its footer wraps instead of clipping the hint mid-sentence and the Add leads button no longer breaks across two lines, campaign status labels move to a shared components/app/campaigns/status module, the Segments, Categories and Segment pages plus every unmapped settings and onboarding route get a document title so they stop reading Page not found, and POST /contacts takes a segments array that pins the new contacts in as include overrides (validated before the write, best-effort after it) which the New contact dialog sends when opened from a segment page
|
2026-09-01 02:57:30 -07:00 |
|
Matthew Meszaros
|
8db032a656
|
feat: address review on segment-campaign linking: a campaign_lead_removals table (migration 000115, orgtransfer-registered) records hand-removed leads so the automatic segment sync never re-adds them while a manual add or the one-shot enrol clears the record, PUT /campaigns/:id/segments rejects an omitted segment_ids so {} cannot detach everything, rows.Err checks on the four new link queries so a truncated read cannot commit as success, write-path enrolment syncs detached from the request goroutine with a per-org in-flight dedupe, per-campaign 30s deadlines in the sweep instead of one shared budget, and an error-with-retry state in the linked-segments dialog
|
2026-09-01 00:33:42 -07:00 |
|
Matthew Meszaros
|
66a105fb9f
|
feat: make segments a live campaign audience and round out lead management (issue #277): a campaign_segments link table with GET/PUT /campaigns/:id/segments (max 20, replace semantics), immediate enrolment of linked-segment members as leads plus targeted re-syncs on contact writes, segment edits, pin-ins and imports and a 2-minute backend sweep for drift, waking active campaigns and restarting completed ones through the full launch checks; a Segments manager dialog and toolbar button on the campaign Leads tab; Remove from campaign as the row action and a selection-bar bulk action so leads can leave a campaign without deleting the contact; campaign and segment pickers in the CSV import wizard backed by a new segment_ids import option that pins imported rows as include overrides; a delete guard naming linked campaigns; orgtransfer registration, live repo tests and docs for all of it
|
2026-08-31 08:45:27 -07:00 |
|
Matthew Meszaros
|
773a3ecaf2
|
Merge remote-tracking branch 'origin/main' into fix/account-reverification-button-missing
|
2026-08-31 06:26:40 -07:00 |
|
Matthew Meszaros
|
c2f5cc4e9e
|
feat: address review on the reconnect flow: a reauth whose repeat consent omits the refresh token now refuses (typed error) when the stored one cannot be read instead of sealing an empty string over it, and credential errors resolve only after the reactivation Update succeeds so a failed reactivation keeps the banner and its reconnect button
|
2026-08-31 06:26:39 -07:00 |
|
Matthew Meszaros
|
70ca90a3e0
|
feat: fix the reconnect reactivation 500 found in live testing: reconnectAccount now loads the row via GetByID itself because the org-scoped Get selects no user_id and the owner-scoped Update was handed an empty uuid; UpdateSMTPIMAPCredentials switches to GetByID with an explicit tenancy check, and the test stub's Get now mimics the real partial row
|
2026-08-31 05:53:12 -07:00 |
|
Matthew Meszaros
|
6918ee06b0
|
Merge remote-tracking branch 'origin/main' into fix/issue-273
|
2026-08-31 05:49:38 -07:00 |
|
Matthew Meszaros
|
5abfb69882
|
feat: address Greptile review on #282: re-measure useFlipPlacement/useFlipAlignment on window resize so an open popover keeps a valid side when the toolbar reflows, and trim the unibox body-key and hook comments down to their one-line invariants
|
2026-08-31 05:36:24 -07:00 |
|
Matthew Meszaros
|
896fc60f4d
|
feat: add the missing mailbox re-verification flow (issue #274): POST /emails/onboarding/oauth/reauth/:id re-runs the provider consent for an existing Gmail/Outlook mailbox (login_hint preselect, same-address check, token rewrite keeping the stored refresh token) and PUT /emails/onboarding/smtp-imap/:id replaces SMTP/IMAP credentials after live validation; both resolve the credential-class error rows and reactivate the mailbox onto its worker, the drawer's Needs attention banner gains Re-authorize and Update credentials buttons, and the mailboxes guide documents reconnecting
|
2026-08-31 05:29:35 -07:00 |
|
Matthew Meszaros
|
760cb5651c
|
feat: fix issue 273: read unibox message bodies from the key the worker actually writes (users/<owner>/emails/<mailbox>/<message>.emsg via config.StorageEndpointEmailBody) instead of the never-written emails/<owner>/<id> key, so opened messages render the full stored plain+HTML body with its line breaks instead of degrading to the collapsed one-line snippet with the truncated-message notice; thread the mailbox account id through GetBody, GetByID and the body-text search backfill, drop the dead PutBody/GetEmailKey, and in the contact activity detail stop content escaping the drawer: measured left/right alignment for the date-range popover (new useFlipAlignment hook) and wrap-anywhere on expanded detail values so long URLs cannot widen the grid past the card
|
2026-08-31 03:54:59 -07:00 |
|
Matthew Meszaros
|
d7a17a0149
|
feat: make the per-mailbox daily campaign cap configurable up to 5000 (issue #276): raise campaign_limit, campaign daily_limit and ramp start/ceiling validation to config.LimitMax, warn in the dashboard above 100/day, and update aitools, zapier and docs copy to match
|
2026-08-31 03:50:36 -07:00 |
|
Matthew Meszaros
|
7a7c6051bc
|
Merge remote-tracking branch 'origin/main' into feature/contact-segments
|
2026-08-30 02:07:56 -07:00 |
|
Matthew Meszaros
|
3bd9325bee
|
feat: address review on segments: gate the add-to-campaign and from-segment actions behind the manage-campaigns permission on the segment page, the segments list and the campaign Leads tab so the dashboard never offers an enrolment the API would refuse, trim the segment package and migration comments to the one invariant they carry, and drop the em dash from the editor comment
|
2026-08-30 00:11:19 -07:00 |
|
Matthew Meszaros
|
52916ab60d
|
feat: verification evidence engine and Greptile fixes for #264: contact_verification_evidence ledger (migration 000111) fed by clean deliveries, human opens, clicks, replies and recipient-naming bounces from the send, tracking, reply and bounce paths, a decaying score that lets real mail outrank a probe and a newer bounce outrank older engagement, verification_confidence on every contact with a reasons list and animated Deliverability card in the contact drawer, per-organization probe breakers, undeliverable counts that ignore finished leads, tighter wiring comments, and docs
|
2026-08-29 23:49:33 -07:00 |
|
Matthew Meszaros
|
daf946fc45
|
feat: add the add_to_segment and remove_from_segment sequence action steps, executed on both the scheduled campaign path and the instant reply path, wired into the backend and consumer
|
2026-08-29 23:45:05 -07:00 |
|
Matthew Meszaros
|
87b3a00aa2
|
feat: add the segment service with create/update validation, nested reference and loop checks, delete refusal when another segment depends on it, preview, member overrides, campaign enrolment that wakes the campaign, and the field catalog with the org's custom fields
|
2026-08-29 23:45:05 -07:00 |
|
Matthew Meszaros
|
3391f6a4ec
|
feat: add the segments and segment_members tables (migration 000110) and register both in the workspace export/import spec under the Contacts group
|
2026-08-29 23:45:05 -07:00 |
|
Matthew Meszaros
|
a02ff7c936
|
feat: address verification overhaul for #264: MillionVerifier as a pay-as-you-go integration plugin with org-sealed key and automatic fallback to the built-in check, built-in prober gains domain cache, Microsoft/Yahoo fingerprinting, MX fallback, disposable/role sub-statuses and a self-check breaker, imports and POST /contacts accept verification results in any known provider vocabulary with auto-detected columns, verdict provenance and expiry columns (migration 000110), campaigns park at paused_undeliverable with re-verify/send-anyway instead of finishing, POST/GET /contacts/verification bulk actions and overview, launch gate override via acknowledge_list_risk, animated verification marks, banner and settings card in the dashboard, and docs
|
2026-08-29 23:11:22 -07:00 |
|
Matthew Meszaros
|
44b2c18906
|
feat: address review on cloud-managed mailboxes: the consumer now asks the cloud to vouch for a warmup token in a mailbox it warms (GET /pool-link/instance/mailboxes/:id/warmup-tokens/:token) and files anything unverified as ordinary mail instead of dropping on a sender-controlled header, disconnect keeps local mirrors and the link until the cloud confirms the instance is released so managed mailboxes cannot be stranded, and long narrative comments are cut to one line
|
2026-08-29 10:07:36 -07:00 |
|
Matthew Meszaros
|
2a831e9783
|
feat: let a linked self-hosted instance sign Google and Microsoft mailboxes in through Warmbly Cloud's own OAuth apps and send with cloud-brokered access tokens: the cloud runs the consent (pool_link_mailboxes.managed, brokered state in Redis, the existing /addresses/*/callback completes it and redirects to the instance's /cloud-oauth/done), keeps the refresh grant, mints short-lived tokens at /pool-link/instance/mailboxes/:id/token and refuses them for revoked links, removed, inactive or blocked mailboxes; the instance mirrors such mailboxes without a credential (cloud_link_mailboxes.managed), ships them to the worker as brokered so goog/msgraph init on a token source that pulls from /api/v1/internal/cloud-link/token/:id, lets the consumer ignore cloud warmup tokens for enrolled mailboxes, and can adopt mailboxes connected directly on the workspace; Add account shows the cloud path and the adoptable list, and the Warmbly Cloud guide documents the model
|
2026-08-29 09:50:52 -07:00 |
|
Matthew Meszaros
|
d621d9b79b
|
feat: sign a new account in as part of registration: createAccount returns the user, both the verification-free and the code-confirm paths mint a session through finishLoginAs (2FA challenge included), /auth/register/confirm answers with that session instead of 204, and the dashboard drops into the workspace right after signing up instead of bouncing to the sign-in form
|
2026-08-29 08:41:34 -07:00 |
|
Matthew Meszaros
|
152f626469
|
feat: remove the free trial: a new hosted workspace starts on the free plan with no time limit, may hold up to 10 mailboxes (connected directly or through a linked self-hosted instance) and warm them in the free pool, and everything else (campaigns, inbox, contacts, CRM, integrations, API keys, audit) sits behind a Starter lock in the sidebar and a SubscriptionGate overlay until a plan is active; the pool-link allowance now counts every mailbox in the workspace, and the marketing site and guide say free workspace instead of 14-day trial
|
2026-08-29 08:34:22 -07:00 |
|
Matthew Meszaros
|
3bdb0fb82d
|
feat: address the Greptile review on the pool link: require https for the cloud URL (loopback exempt for local development) since the instance token and mailbox passwords travel on it, remove the cloud copy when the local enrollment row cannot be written so a mailbox never warms in both places, delete the local enrollment row before the cloud one and restore it if the cloud call fails so a mailbox is never left with no warmup anywhere, and trim the new multi-line comments to the one-line style
|
2026-08-29 07:17:10 -07:00 |
|
Matthew Meszaros
|
b75fdcf86c
|
Merge remote-tracking branch 'origin/main' into feature/self-hosted-warmup-pool-access
# Conflicts:
# internal/scheduler/warmup_scheduler.go
|
2026-08-29 07:10:38 -07:00 |
|
Matthew Meszaros
|
37b60b59d3
|
feat: let a self-hosted instance warm its mailboxes in the hosted pool: device-code link approved at /connect, instance-token API that enrolls SMTP/IMAP mailboxes as warmup-only accounts (no history import, non-warmup mail dropped), free for 10 mailboxes and unlimited on the seeded $15 pool plan, tier fallback to proven healthy mailboxes when a pool runs thin, local warmup stands down for enrolled mailboxes, Settings > Warmbly Cloud step flow and linked-instances page, docs guide, marketing copy, and fix SetWarmupLifecycle re-reading the row with an org-scoped lookup so every warmup start/pause returned 404
|
2026-08-29 07:09:04 -07:00 |
|
Matthew Meszaros
|
e84d47c492
|
feat: drop a page hit instead of storing it under the wrong contact when tying a browser to a ticket fails after a lost identification race (attach now fails closed and the edge retries), and make the edge dedupe an atomic request-owned claim so a failed concurrent forward can only release its own entry and never a successful request's
|
2026-08-29 05:20:26 -07:00 |
|
Matthew Meszaros
|
5821fc2a1c
|
feat: merge origin/main into the website tracking branch after PR #260 landed 000106_contact_source, renumber the website tracking migration to 000107, and fold the page_hit timeline type, its query source and its Website filter, meta line and expandable details into the reworked contact Activity tab and models from the contact-activity PR
|
2026-08-29 04:38:35 -07:00 |
|
Matthew Meszaros
|
8daefbe8c4
|
feat: address the Greptile review on website tracking by believing forwarded-IP headers only from TRACKING_TRUSTED_PROXIES (socket peer otherwise, proxy-appended last X-Forwarded-For entry, applied to pixel, click and page-hit paths), making IdentifyVisitor report whether it claimed the row so a lost identification race re-reads the visitor and splits onto a fresh record instead of attributing the hit to the wrong contact, forgetting the edge dedupe entry when a forward to the backend fails so the retry is counted, and trimming the new Go and Rust comments to the one-line style
|
2026-08-29 04:16:28 -07:00 |
|
Matthew Meszaros
|
9fd9e082ae
|
feat: address Greptile on PR #260 by only linking meeting join_url values whose scheme is http or https in the contact timeline (both the meta Join link and the expanded detail), projecting the campaign's daily ramp advance in memory before the read-only next-send preview so a preview on a new UTC day budgets with the level the next scheduler pass will persist (with a unit test), and trimming the campaign-state model, preview and service comments to one-line invariants
|
2026-08-29 03:51:24 -07:00 |
|
Matthew Meszaros
|
896dc4fe3c
|
feat: merge origin/main (campaign lead engagement filters, contact export org scoping) into the issue #255 branch, keeping both the engagement and the next-action sections of the campaigns guide
|
2026-08-29 03:40:13 -07:00 |
|
Matthew Meszaros
|
7d33544540
|
feat: pass the organization id, not the user id, from ExportContacts through contactService.Export into contactRepository.ExportAll, because contact Search became organization-scoped with the issue #187 fix and every contact export since then returned only the header row, which also left the new campaign lead_* export columns with nothing to fill (issue #250)
|
2026-08-29 03:30:59 -07:00 |
|
Matthew Meszaros
|
a4739b63ec
|
feat: add first-touch contact source attribution (migration 000106: contacts.source with a CHECK, source_detail, first_seen_at, existing rows stamped unknown) stamped at every creation site (dashboard manual/campaign, file import, Google Sheets sync, API key, AI assistant), write contact_created / campaign_added / campaign_removed / category_added / category_removed lifecycle events into contact_activities inside the same transactions as the links and merge them into the contact timeline, refactor FindNextRoutedPair's routing into a campaignRouter shared with a per-contact RouteContact and split CalculateNextCampaignTime into campaignSenders + placeCampaignSend so a read-only PreviewContactSend derives a contact's next step through the scheduler's own constraints, expose it as GET /contacts/:id/campaigns behind the contact service, render a per-campaign state panel, Campaigns/Lifecycle filter chips and expandable event rows in the Activity tab plus a Source section on Overview, cover it with TestLive* for the timeline events and the next-action preview (step wait, sending window, paused), and document it in the contacts, campaigns and API reference docs (issue #255)
|
2026-08-29 03:28:16 -07:00 |
|
Matthew Meszaros
|
27630eec0a
|
feat: add website visitor tracking for issue #255 with migration 000106 (website_tracking_settings, website_visitors, website_page_hits, all registered in the orgtransfer spec), a consent-gated dependency-free tracking.js served by the Rust tracking service with a rate-limited, size-capped, prefetch-filtered POST /p ingest that forwards to a new backend internal page-hits endpoint for server-side user-agent and GeoIP enrichment, contact identification only through the click ticket the redirect appends to registered hosts, a per-workspace retention job, page_hit events with an expandable detail view in the contact Activity timeline, a Settings > Website tracking page for the snippet and consent, location and retention configuration, realtime PAGE_HIT fanout, and a website tracking guide plus endpoint, export and configuration docs
|
2026-08-29 03:25:50 -07:00 |
|
Matthew Meszaros
|
d67cdb8562
|
feat: add recipient-level engagement to the campaign Leads view by adding an engagement search filter (opened, not_opened, clicked, not_clicked, replied, not_replied, bounced) that composes with lead_status as AND and rejects unknown values with stable 400 codes, counting only human opens so machine opens never read as engagement, adding contacted/opened/clicked/replied_any totals to lead_counts, rendering Opened/Clicked/Replied columns and clickable server-backed status and engagement chips in the Leads table with matching Filters sheet sections, offering lead_status/lead_opened/lead_clicked/lead_replied export columns from a campaign, covering every filter value with a live Postgres test, and documenting the columns, filters, error codes and export fields (issue #250)
|
2026-08-29 03:12:02 -07:00 |
|
Matthew Meszaros
|
d9b4220bf0
|
feat: drive the resting notice in the mailbox drawer off warmup_health presence, which is the pool row the rebalancer reads, instead of warmup_status, which stays present for paused or unentitled warmup, condense the new lifecycle and drawer comments to one line, and correct the mailboxes guide so it says a rest ends without a signal only when the mailbox leaves its pool, since pausing warmup keeps it there
|
2026-08-29 02:09:49 -07:00 |
|
Matthew Meszaros
|
3552fffae1
|
feat: stop stranding a resting mailbox whose warmup stopped by letting lifecycle.Decide run the rest clock on an unknown health signal and resume the mailbox after the 72h window instead of re-stamping probation on every tick, make POST /emails/:id/release the manual exit from resting with its own reason, add a Put back into campaigns action and warmup-aware copy to the resting notice in the mailbox drawer, and document the automatic and manual exits in the mailboxes guide, API reference and endpoint map (issue #243)
|
2026-08-29 02:04:18 -07:00 |
|
Matthew Meszaros
|
4cee178c7a
|
Merge remote-tracking branch 'origin/main' into fix/issue-244-reserve-hold
|
2026-08-28 23:33:26 -07:00 |
|
Matthew Meszaros
|
35e8f95f2a
|
feat: make a release from the mailbox hold land where the rebalancer would put it by running lifecycle.Decide against the mailbox's current warmup health through a new GetLifecycleCandidate repository read, so an unhealthy mailbox goes straight to resting instead of sending cold for up to an hour before the hourly pass rests it again, report that outcome in the drawer toast and the API reference, and condense the new hold comments in the email service, backend wiring, repository, hook, client and drawer to the one-line form the repo convention asks for
|
2026-08-28 23:30:15 -07:00 |
|
Matthew Meszaros
|
84700bae8d
|
Merge remote-tracking branch 'origin/main' into fix/issue-241
# Conflicts:
# cmd/consumer/main.go
# docs/content/docs/guides/deliverability.mdx
# internal/app/auth/login_risk.go
# internal/app/auth/provision.go
# internal/app/contact/import.go
# internal/app/orgrisk/service.go
|
2026-08-28 23:06:07 -07:00 |
|
Matthew Meszaros
|
6bb2a3d0c2
|
Merge remote-tracking branch 'origin/main' into fix/issue-245-resolution
|
2026-08-28 22:51:37 -07:00 |
|
Matthew Meszaros
|
4ead1b200f
|
feat: answer the review on #251 by filing a signup's throwaway-domain finding separately from its soft ones so the aggregate score no longer carries one class, and by measuring import quality across everything a workspace has imported instead of the newest file, so a small clean upload cannot retract a large bad list whose addresses are still stored while the finding still fades as good data outweighs it, with the running counts kept as evidence on the finding and a finding filed before those counts existed folded in as the smallest list that could have flagged it
|
2026-08-28 22:51:23 -07:00 |
|
Matthew Meszaros
|
5c832461b0
|
feat: wire the unreachable reserve send-lifecycle state to a real per-mailbox hold (POST /emails/:id/hold and /release behind manage_emails and WRITE_EMAILS, SetSendHold on the email service forcing the lifecycle past the rebalancer guard, a Hold from campaigns toggle on the mailbox drawer's Overview tab with the reserve notice rewritten to point at it, warmblyctl mailbox hold/release, and docs in the mailboxes guide, API reference and scope map), and drop the warming state nothing ever set from the model, the web type, the docs table and the DB check via migration 000104, which folds any legacy warming row back to active
|
2026-08-28 22:47:31 -07:00 |
|
Matthew Meszaros
|
0ab947dc5d
|
Merge remote-tracking branch 'origin/main' into fix/issue-241
|
2026-08-28 22:47:28 -07:00 |
|
Matthew Meszaros
|
2fd22cafd3
|
feat: answer 404 rather than 500 when a risk mutation names an organization that does not exist, by having every org-risk repository path return nil for a missing row and one resolved() helper turn that into the not-found the GET already gave, and stop showing an admin who holds only view_organizations the Set posture, Lift override and retract controls, which the backend can only ever answer 403, so the Abuse posture panel reads as evidence for them and as evidence plus actions for an admin who can actually act
|
2026-08-28 22:47:24 -07:00 |
|
Matthew Meszaros
|
a5ea55bba5
|
feat: give a suspended workspace a way back, because risk_state was a one-way door: the derived band is no longer pinned at suspended by the UPDATE in pg_org_risk, an operator's decision is now an explicit risk_override that outranks the score and survives every later detector write until it is lifted, the one-shot detectors (signup origin, import list quality, repeated sign-in anomalies) file findings with a 30-day expiry that a six-hourly consumer sweep retires so a score falls on its own, migration 000104 backfills that expiry onto findings already on file, and four admin endpoints plus an Abuse posture panel in admin/ let an operator finally read the evidence the customer endpoint withholds, retract a finding, pin a band and lift the pin; also stops the reviewing admin's identity reaching the tenant's own audit feed (which resolves an actor to a name and email) by recording the platform as the actor there and the operator in the admin trail, and stops risk_signals riding along in a customer-downloadable org export
|
2026-08-28 22:42:57 -07:00 |
|
Matthew Meszaros
|
2b25e32f6b
|
feat: judge an organization's abuse posture on what it did rather than on what it looks like, so an agency opening client workspaces from one office under one operator identity and connecting the mailboxes in an afternoon lands on watch instead of restricted: findings now carry a circumstantial/substantive class, shape is capped at 40 points and can never move a band past watch, the signup address and identity clusters count once as one family, the signup finding is substantive only when the domain is really disposable, list_quality retracts on a later clean import, and the nightly sweep gains a recipient-outcome pass scoring 30-day bounce and complaint rates against the provider bands so a band still has evidence it may act on
|
2026-08-28 22:41:44 -07:00 |
|
Matthew Meszaros
|
bb5809a035
|
Merge remote-tracking branch 'origin/main' into fix/issue-247-report
|
2026-08-28 22:12:18 -07:00 |
|
Matthew Meszaros
|
3d72c7386e
|
feat: condense the explanatory comments on the signature editor DOM sync, the patchEmailLists cache helper and the org-scoped auth-check lookup to the one-line form the repo convention asks for
|
2026-08-28 22:09:18 -07:00 |
|
Matthew Meszaros
|
6da2c3dfaa
|
feat: fix the three mailbox Settings bugs from #247: patch both cache shapes under ["emails", "list"] (paginated pages and the flat directory array) through one patchEmailLists helper so saving, removing, warmup lifecycle and tracking-domain writes no longer crash on undefined.map, stop rewriting the signature editor's contentEditable innerHTML on every keystroke so the caret stays where the user is typing, and scope the auth-check GET/POST handlers by organization instead of user id so the SPF/DKIM/DMARC check stops returning 404
|
2026-08-28 22:05:41 -07:00 |
|
Matthew Meszaros
|
bd126e7c89
|
Merge remote-tracking branch 'origin/main' into fix/issue-144-resolution
|
2026-08-28 22:03:23 -07:00 |
|