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
|
315509d0da
|
feat: replace the purple lock card with a full-screen plan chooser on the auth screen's sky (warm 10 mailboxes free, self-host and link, or choose a plan), give the hosted Accounts page a two-path panel (connect a mailbox here, or self-host for free and link the instance) that collapses to a usage strip once mailboxes exist, drop indigo from the access dialog, describe the free plan honestly in the catalog, and default the transactional email footer to the real company details (Mindroot Ltd, 16543299, 71-75 Shelton Street)
|
2026-08-29 08:48:13 -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
|
b053a1ca80
|
feat: surface the Warmbly Cloud link where self-hosters already are: a skippable last onboarding step with the shared link card, a connect banner and dialog on the Mailboxes page, a Cloud badge plus the cloud's daily count and warm/pause/resume/remove items in each row's warmup menu, a cloud block in the mailbox drawer that replaces the local controls for enrolled mailboxes, the coverage notice pointing at the dialog instead of the marketing site, reads on /cloud-link open to any member and per-mailbox enrollment gated on manage mailboxes, and the guide updated for the new entry points
|
2026-08-29 08:05:11 -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
|
5938a4af82
|
feat: renumber the drop-warming-lifecycle migration from 000104 to 000105 after 000104_org_risk_override reached main first, and merge origin/main into the branch so check-migrations sees one version per number again
|
2026-08-28 23:33:47 -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
|
c9693dabce
|
Merge remote-tracking branch 'origin/main' into fix/issue-241
|
2026-08-28 22:51:44 -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
|
74fdd5002d
|
feat: condense the new comments on PoolSpamPlacementsByProvider, its unknown bucket, the blank-domain placement filter and the summary model field to the one-line form the repo convention asks for, keeping the non-obvious constraint (the stored recipient_provider is the connect method, and a domainless placement belongs to no provider) and dropping the narration
|
2026-08-28 22:46:25 -07:00 |
|
Matthew Meszaros
|
771bbe04e4
|
feat: correct the comment on the partner-routing fixture's service wiring, since the org-risk read now runs ahead of the stored warmup tier and it is that read failing open, not the tier short-circuiting, that lets the selector run without an org-risk repository
|
2026-08-28 22:42:59 -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
|
6c172daaef
|
Merge remote-tracking branch 'origin/main' into fix/issue-143-investigation
|
2026-08-28 22:42:25 -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 |
|
Matthew Meszaros
|
ec7b7bbed3
|
feat: count anchors and bare URLs together instead of taking the larger of the two, so three labeled links plus three written-out URLs no longer score a clean 100, clear the editor's pending flag when the draft is emptied mid-request since the cancelled request's finally can no longer do it, fail the preflight content check when a campaign's attachments cannot be read rather than scoring as if there were none and passing copy the send path then warns about, and condense the new comments to the one-line form the repo convention asks for
|
2026-08-28 21:59:53 -07:00 |
|
Matthew Meszaros
|
4ddcb24bcd
|
feat: make the campaign content check actually fire, fixing a link cap that counted zero links on every HTML email because the URLs live in href attributes that tag-stripping discards, a preflight that scored wait and action nodes as copy and so failed every campaign using one at 55/100 because GetSequencesByCampaignID never selected the kind column, a content_warning log level of "warning" that never matched the dashboard's "warn" amber tier, an unvalidated min_content_score an API caller could set to 5000, attachments the send path scored but preflight ignored, and a docs claim of live editor scoring that was really a manual button
|
2026-08-28 21:54:29 -07:00 |
|
Matthew Meszaros
|
230d80db64
|
feat: condense the new warmup pool comments in email_task.go, handler.go, service.go, pg_worker.go and warmup_scheduler.go to the one-line form the repo convention asks for, keeping the non-obvious constraints (the stored tier is never empty, the tier column records what is paid for rather than where the mailbox warms) and dropping the narration
|
2026-08-28 21:31:06 -07:00 |
|
Matthew Meszaros
|
f518c950d7
|
feat: stop an unattributable warmup spam placement demoting every custom-domain partner, key the admin pool rollup to who runs the recipient's mail rather than the connect method, and cover issue #143's routing feedback end to end with a live selectWarmupPartner test that proves the per-provider signal reaches the partner the selector returns
|
2026-08-28 21:30:52 -07:00 |
|
Matthew Meszaros
|
c28ef3e82a
|
feat: keep a restricted or suspended workspace out of the premium warmup pool by checking the organization's risk posture before the stored warmup_pool_type in resolveWarmupPoolType (tasks and email services), which is never empty so the restricted branch below it was dead code, make the scheduler's recipient-capacity count risk-aware the same way, wire the org risk repository into the email service, and refuse the premium membership move in UpdateEmailAccountWarmupPoolType while the owning organization is restricted so a worker rebalance cannot readmit a risky tenant
|
2026-08-28 21:26:57 -07:00 |
|
Matthew Meszaros
|
5e0916b266
|
feat: count only readable messages in the warmup batch failure suffix, so a blank provider error entry no longer renders "(+1 more)" and sends the operator hunting for a second reason that does not exist, and condense the new BatchState, batchFailureReason and failed-batch comments to the one-line form the repo convention asks for
|
2026-08-28 20:03:07 -07:00 |
|
joao-crm
|
0e4a696c2f
|
feat: record the provider's own reason when a warmup generation batch fails as a whole, instead of leaving the job saying only "batch failed": a batch that never ran has no error file to read the refusal out of, so the message OpenAI puts on the batch itself is the only account of why, and GetBatch now returns it as part of a BatchState the poller writes into the job row
|
2026-08-29 01:12:22 +00:00 |
|
Matthew Meszaros
|
becbc5bb18
|
Merge remote-tracking branch 'origin/main' into feat/149-ato-signals
# Conflicts:
# docs/content/docs/guides/security.mdx
|
2026-08-28 12:26:55 -07:00 |
|
Matthew Meszaros
|
61f7b9cd3e
|
feat: condense the correlate package and Run comments to the local constraint, moving the cross-account rationale out of production code
|
2026-08-28 12:21:59 -07:00 |
|
Matthew Meszaros
|
70d49e7c5f
|
feat: record the verdict that issued the login challenge instead of re-judging at confirm, and stop a mailbox in no warmup pool reading as healthy: the emailed code is confirmed by a second request that can carry a different address and sees history the first one did not, so re-assessing there could file a challenged sign-in as clean and drop it from the repeat count that reaches the workspace posture; the verdict now travels in the login session and every completion path takes it, while ListLifecycleCandidates returns a NULL health as unknown so leaving the pool is no longer evidence of recovery and a resting mailbox neither resumes nor accrues probation on it
|
2026-08-28 12:20:11 -07:00 |
|
Matthew Meszaros
|
b8ddf2bc83
|
chore: merge main, keeping both background jobs
|
2026-08-28 12:12:51 -07:00 |
|