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
|
7b6830daa5
|
feat: say segments out loud in the API scope catalog: the READ_CONTACTS and WRITE_CONTACTS descriptions served to the API key dialog (and the docs permission table) now name segments, since segment CRUD, previews, member overrides and enrolment ride on the contact scopes and the picker never said so
|
2026-09-01 01:32:56 -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
|
9d9c126b76
|
feat: hyphenate the 24-hour window compound in the mailboxes guide gap-throughput note
|
2026-08-31 05:39:53 -07:00 |
|
Matthew Meszaros
|
a657da3a1d
|
Merge remote-tracking branch 'origin/main' into fix/cold-email-sending-defaults
|
2026-08-31 05:39:46 -07:00 |
|
Matthew Meszaros
|
c28179e61f
|
feat: raise the campaigns ramp_start/ramp_ceiling DB CHECK constraints to 5000 (migration 000113) so raised API bounds cannot 500 on the constraint, validate the effective ramp pair against stored values on partial updates, and fix the campaign/mailbox guides on the effective cap and the 600s gap throughput bound
|
2026-08-31 05:31:13 -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
|
f35d7e5037
|
feat: address CodeRabbit review on the segment ID chip: narrow the API reference sentence to endpoints that operate on an existing segment, add an aria-label and a polite sr-only status to the copy button so the copied state reaches assistive technology
|
2026-08-31 04:19:20 -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
|
55156a8964
|
feat: surface the segment ID for API integrators: a click-to-copy ID chip in the segment page header, a Copy segment ID entry in the segments list row menu, a Segments in the API section in the segments guide pointing at the CRUD, members and enrolment endpoints, and a note in the API reference on where the dashboard shows the ID
|
2026-08-31 03:47:12 -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
|
b99e77c389
|
feat: replace the contact filter side sheet with an inline interactive filter bar: always-visible Category, Segment, Status and Campaign pills that apply on the spot, an Add filter menu for custom fields, date added, last updated, campaign count and (on Leads) lead status and engagement, editable pills with remove, a live matching count, Clear and Save as segment, half-filled custom-field pills kept out of the request, and docs for filtering the list
|
2026-08-30 01:57:38 -07:00 |
|
Matthew Meszaros
|
7dc6a9a26c
|
feat: fold segments into the contacts area instead of a separate nav item: a Contacts layout with All contacts, Segments and Categories tabs, segment pages moved to /app/contacts/segments, a new Categories tab with live counts, rename, color, create and delete, a Save as segment button in the contact filter panel that opens the editor with the filters mapped to conditions, and ?category= pre-filtering of the contact list
|
2026-08-30 01:50:57 -07:00 |
|
Matthew Meszaros
|
072ff1a39c
|
Merge branch 'main' into fix/issue-265
|
2026-08-30 01:05:03 -07:00 |
|
Matthew Meszaros
|
802c0913b0
|
feat: address review on the bare-metal guide: bind NATS to loopback through /etc/nats.conf and document token plus TLS auth before opening it to remote workers, clone and build as the unprivileged user with sudo install into /opt/warmbly/bin, and issue certificates with certbot certonly --standalone before enabling the nginx site
|
2026-08-30 00:21:16 -07:00 |
|
Matthew Meszaros
|
2e376bcd4a
|
feat: make the tracking publisher honor user:pass@ or token@ credentials in NATS_URL by lifting them into async-nats ConnectOptions, since async-nats ignores URL userinfo unlike the Go client, and redact the userinfo from the connect log line
|
2026-08-30 00:21:16 -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
|
50663ef652
|
feat: document the segments endpoints, condition format, segment_ids search filter and the add/remove-segment sequence actions in the API reference, and note that segments travel in workspace archives
|
2026-08-29 23:45:06 -07:00 |
|
Matthew Meszaros
|
abd2ebca97
|
feat: document segments in a new guide (building, overrides, sequence actions, using, limits), register it in the guides index and link it from the contacts guide
|
2026-08-29 23:45:06 -07:00 |
|
Matthew Meszaros
|
393caf6b3a
|
feat: link the Compose self-hosting guide's Beyond compose section to the new Deploying without Docker page and the deploy/systemd and deploy/nginx assets
|
2026-08-29 23:37:22 -07:00 |
|
Matthew Meszaros
|
e1599f6dc0
|
feat: register the bare-metal page in the Self-hosting docs section directly after the Compose deployment guide
|
2026-08-29 23:37:22 -07:00 |
|
Matthew Meszaros
|
cc6783cb0a
|
feat: add docs page Deploying without Docker covering backing services, building the Go, Rust, Elixir and Vite artifacts from source, hand-written config.js, warmbly.env and worker.env, systemd, nginx, claiming, native remote workers, upgrades, backups and troubleshooting
|
2026-08-29 23:37:22 -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
|
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
|
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
|
1247c712f3
|
feat: add make poollink-dev, a two-instance dev environment for the Warmbly Cloud link (a prod-like cloud with DEPLOYMENT_MODE=cloud, Stripe gates on with placeholder keys, Mailpit email verification and the Sunrise Labs pool seeded, next to a fresh unclaimed self-hosted instance whose WARMBLY_CLOUD_URL points at it), with status/setup-link/down/reset subcommands and a local-development docs section
|
2026-08-29 07:14:04 -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
|
111a5a4034
|
feat: read the forwarded client address from exactly one operator-named header (TRACKING_CLIENT_IP_HEADER, default x-forwarded-for with the proxy-appended last entry, cf-connecting-ip only when configured) so a client-supplied CF-Connecting-IP passed through a generic trusted proxy can no longer choose the rate-limit bucket or the stored page-hit location, with tests and the configuration, env example and compose entries
|
2026-08-29 05:11:22 -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
|
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
|
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
|
934a4868a5
|
feat: correct the operator docs after the class-model merge, since import quality is no longer a finding that ages out: it is a running assessment across everything a workspace has imported and withdraws itself, so the expiry paragraph now names the detectors that genuinely have no way back (a signup's origin, a run of anomalous sign-ins) and says which kind the panel's Ages out column is reporting
|
2026-08-28 23:08:39 -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
|
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 |
|