Commit Graph
673 Commits
Author SHA1 Message Date
Matthew Meszaros 3bf8d4bdfa Merge remote-tracking branch 'origin/main' into feature/mailbox-fair-use-allowance 2026-09-04 21:25:53 -07:00
Matthew Meszaros 70baaac636 feat: describe a paid plan's mailbox capacity in the free-workspace panel of the allowance dialog as one mailbox for every daily send it includes rather than promising unlimited, and name the upgrade feature More mailboxes to match 2026-09-04 21:17:58 -07:00
Matthew Meszaros bf46839fb7 feat: address the CodeRabbit review on the mailbox allowance work by reserving the mailbox slot inside the account insert transaction under a per-organization lock so concurrent connects cannot both take the last one, resolving the storage limit under the quota lock so a plan change cannot be raced past, deleting refused attachment objects on a context that outlives the request, settling already-connected rows before the bulk batch spends allowance, leaving password columns out of the failed-rows CSV, stopping click propagation from the portalled allowance dialog, and counting bulk progress once 2026-09-04 21:11:25 -07:00
Matthew Meszaros 3596a9770f feat: make mailboxes truly unlimited under a fair-use allowance of one mailbox per daily send the plan includes (15,000 on Business), drop the 200 hard cap and the 5-per-day connect throttle, open a request-more dialog with inline limit requests and plan upgrade when the allowance is reached, add bulk CSV mailbox import with live progress and a downloadable failed-rows file, reserve attachment storage atomically under a per-org lock so concurrent uploads cannot pass the quota (#326), report storage and the allowance on the billing usage meters, and honor approved daily-send overrides in the sender 2026-09-04 21:00:26 -07:00
Matthew Meszaros f68918d7f6 Merge remote-tracking branch 'origin/main' into feature/warmbly-cli 2026-09-04 20:14:46 -07:00
Matthew Meszaros 028689fd2e feat: device-code sign-in for the CLI, with a browser approval page at /cli that mints a scoped API key, self-revocation at DELETE /api-keys/self so a read-only credential can always end itself, and app_url plus websocket_url on /auth/config so a client can find the dashboard and the realtime gateway on a self-hosted layout 2026-09-04 20:14:27 -07:00
Matthew Meszaros aba176f8aa fix: never highlight a plan at or below the workspace's current one, so an Enterprise customer is no longer shown Business as the best value, and stop the agencies page promising each client its own machine and IP now that the FAQ states we do not sell or manage IPs as a product 2026-09-04 09:20:33 -07:00
Matthew Meszaros 5fd9aedb80 Merge remote-tracking branch 'origin/main' into feature/plan-upgrade-paywall-modal 2026-09-04 09:11:51 -07:00
Matthew Meszaros 5c4f87c1aa feat: list the campaign's unscoped attachments under a Sent with every step group in the step composer, so files the step really carries stop being invisible in the panel that claims to show them, and say in the confirm that removing one takes it off every step 2026-09-04 06:40:48 -07:00
Matthew Meszaros af9e852314 Merge branch 'main' into fix/step-attachments-mount 2026-09-04 06:34:53 -07:00
Matthew Meszaros d78e4423e4 fix: address the review of the notifications work — bound detached alert delivery to a fixed pool that drops and logs rather than spawning a goroutine per event, never echo a transport error that embeds the webhook URL, treat an emptied channel target as cleared so switching transport cannot post Slack payloads to a Discord URL, emit user.registered for invited signups and the worker alert after a successful reassignment, format Stripe amounts by currency exponent, refuse an upgrade while the plan list is still loading instead of silently opening the portal, apply a fixed discount to the annual invoice rather than to each month, convert proration from minor units, contain focus in the nested enterprise dialog, hide the odometer wheels from assistive tech, and drop the last dedicated-IP row from the pricing matrix 2026-09-04 06:33:20 -07:00
Matthew Meszaros 85f3dad215 feat: send the files a step actually carries by honouring campaign_attachments.sequence_id in the send, test-send and preview paths instead of attaching every campaign file to every step, count them per step in the preflight content score, and refuse an upload naming another campaign's step 2026-09-04 06:16:16 -07:00
Matthew Meszaros 81d6ba4520 Merge remote-tracking branch 'origin/main' into feature/plan-upgrade-paywall-modal 2026-09-04 06:09:14 -07:00
Matthew Meszaros c089cdfa75 fix: address review findings on the upgrade surfaces — resolve a catalog plan to the server plan by exact name before falling back to an unambiguous prefix so an operator-added 'Starter Legacy' cannot capture Starter's checkout, stop a plan that does not unlock the requested feature from offering to buy it, honour the dialog's aria-modal by moving and containing focus and restoring it on close, and pin the odometer digit geometry against flex shrink 2026-09-04 06:09:09 -07:00
Matthew Meszaros a0c9d5a5b0 feat: add operator notification channels an admin configures in the panel (Discord, Slack, HMAC-signed webhook, or email) with per-channel event subscriptions, a synchronous test probe, SSRF-guarded URLs and credentials redacted on read, wire nine instance events including the enterprise inquiry that previously wrote a row nobody was told about, and reword the plan-specific limits copy so it reads correctly without billing 2026-09-04 06:02:40 -07:00
Matthew Meszaros 7d8bb08fa0 fix: Talk to sales now opens an enterprise inquiry form instead of a Stripe portal session a free-tier prospect has no customer for, and correct two clients that could never succeed: the portal sent no body against a required return_url and read a url key the endpoint never returns, and the enterprise inquiry sent name/email/company/message against a handler binding company_name/contact_name/contact_email 2026-09-04 05:20:31 -07:00
Matthew Meszaros b3e5394bb5 feat: drop the plan card hover lift and the large hover drop-shadow, leaving the same border-darkens-on-hover the rest of the settings controls use 2026-09-04 05:16:02 -07:00
Matthew Meszaros 87ada2c99f feat: drop the purple gradient and indigo plan accents for the app's own slate and sky palette (Business wears sky, Enterprise solid ink, no gradients anywhere), highlight the best-value plan instead of whatever sits one rung up, remove the duplicated capability matrix from Overview and the comparison table from Plans, and stop calling dedicated workers 'dedicated IPs' across the dashboard, docs, pricing page, FAQ and agencies page 2026-09-04 05:11:47 -07:00
Matthew Meszaros 01dd6e3450 feat: rebuild Billing > Plans on the same large plan cards the upgrade dialog uses (extracted to a shared component with rolling prices, interval sheen and a highlighted next-step-up plan), add a side-by-side comparison table of every limit and capability sourced from the server plan records with the current plan column highlighted, and show the proration preview per card 2026-09-04 05:03:28 -07:00
Matthew Meszaros 19f8dfbc20 feat: rebuild Billing > Overview as a real control panel with cancel and resume, live usage meters against server-enforced limits, a capability matrix, trial and past-due and pending-cancellation banners; price plan switches with a proration preview on the Plans tab; fix the cancel client that sent no body and always 400'd; stop the Payment tab claiming no card is on file 2026-09-04 04:48:59 -07:00
Matthew Meszaros 3cca2390be feat: replace the upgrade dialog's count-up prices with a per-digit odometer that rolls each wheel to its new digit, and choreograph the plan section on an interval flip with a staggered sheen sweep across each card, a scale pulse on the price, sliding billing-note and section-description crossfades, and an annual savings pill 2026-09-04 04:36:03 -07:00
Matthew Meszaros 1533b83c16 feat: open a full-screen upgrade dialog from every plan lock (sidebar rows, the unified inbox frosted overlay, the free-workspace screen) with the four plan cards, a monthly/annual toggle with animated prices, the unlocking plan highlighted, promo codes and one-click Stripe checkout or in-place plan change through a shared useUpgradeFlow hook that the billing page now uses too, return the Stripe redirect to the originating page and settle it in place, and add a plans and billing guide 2026-09-04 04:03:44 -07:00
Matthew Meszaros ebc6de1f81 feat: refetch the campaigns list on a slow cycle while the realtime socket is not connected, so send counts kept current by live invalidation cannot sit on their five-minute cache with no event coming to refresh them, and document what the header's connection dot means and what still refreshes while it is down 2026-09-04 04:03:01 -07:00
Matthew Meszaros 02c8d8ec27 feat: read the header's connection quality from the heartbeat roundtrip the socket provider already measures instead of an interval that compared itself against its own last tick and so reported a healthy socket as a slow one on almost every pass 2026-09-04 04:03:01 -07:00
Skylinerffm97 16d4a13810 feat: mount StepAttachments under the step composer in SequenceView for email steps so per-step attachments can be seen, uploaded and removed from the dashboard, and describe per-step attachments in the sequences guide (issue #307 item 3) 2026-09-04 12:59:19 +02:00
Matthew Meszaros 483d6c2fe7 Merge remote-tracking branch 'origin/main' into fix/issue-307-test-send-preview-from-name 2026-09-04 03:34:35 -07:00
Matthew Meszaros ce85fd6188 feat: clear the rendered step preview alongside the contact and mailbox picks when the editor changes campaign, so the panel cannot keep showing the previous campaign's sender and attachments while the new render is in flight 2026-09-04 03:27:20 -07:00
Matthew Meszaros 43297882f1 feat: clear the step preview's contact and mailbox picks when the editor is pointed at another campaign or at none, and send the contact and account identifiers only alongside the campaign they belong to, so a preview can never carry a selection made for a different campaign 2026-09-04 03:19:54 -07:00
Matthew Meszaros 915390f42a feat: match the closing body tag case-insensitively when placing the mailbox signature so a pasted document written with </BODY> still gets it inside the body, drop a step preview response whose request has been superseded so an out-of-order reply cannot repaint stale copy, and keep pulling mailbox pages in the preview sender picker while an enabled campaign sender is still unaccounted for 2026-09-04 03:11:53 -07:00
Matthew Meszaros 997aa2c748 Merge remote-tracking branch 'origin/main' into fix/issue-307-test-send-preview-from-name 2026-09-04 02:58:40 -07:00
Matthew Meszaros aa55b31422 feat: give the campaign step preview a contact and mailbox picker so it renders for a real lead with that sender's signature, the opt-out footer and the attachment list, add a Send test action that mails the saved step through the chosen mailbox, and document both in the sequences guide 2026-09-04 02:58:36 -07:00
Matthew Meszaros eb85224cc8 Merge remote-tracking branch 'origin/main' into fix/contact-timeline-opaque-cursor 2026-09-04 02:57:59 -07:00
Matthew Meszaros ab0c756aa2 feat: show a campaign's linked segments on the Leads tab as chips with live enrolled and held-out counts that filter the list, explain an empty list caused by an empty segment or hand-removed members and add them back in one click, add a scope-aware Export to campaign leads and segment members with a campaign-ready preset that carries lead status and engagement, write the links and the enrolment of PUT /campaigns/:id/segments in one transaction so a failed enrolment changes nothing instead of answering added 0, restart a completed campaign when a one-shot enrol adds leads, clear the manual-removal record when a contact is added by hand, count every link's members in one scan, audit sweep enrolments so open Leads tabs refresh, and document it in the campaigns and segments guides and the API reference 2026-09-03 20:38:27 -07:00
Matthew Meszaros 8503ad4f37 feat: page the dashboard contact timeline with the server's opaque cursor from pagination.next_cursor instead of deriving a bare timestamp from the last event, in the listContactTimeline client and the useContactTimeline infinite query 2026-09-03 20:19:16 -07:00
Matthew Meszaros 5dbbaad5b5 feat: add the pagination envelope with its opaque next_cursor to the dashboard's ContactTimelineResult type and note that the top-level has_more only mirrors it 2026-09-03 20:19:16 -07:00
Matthew Meszaros 701dc404a2 Merge remote-tracking branch 'origin/main' into fix/click-without-open-tracking 2026-09-03 05:22:05 -07:00
Matthew Meszaros bb35de0455 feat: address review on self-hosted updates: the updater no longer re-locks its mutex when a job finishes (every job used to deadlock at completion and freeze the status API), the backend caches the updater view so the member version pill, the health checks and the admin poll share one read and an absent updater is reported as not running rather than broken, the bare-metal upgrade builds unprivileged and hands off to a root-owned fixed-path installer that refuses symlinks so sudoers allows one command instead of install/cp/rm/chown/chmod/systemctl/ln, the installer fails when the backend does not come back, the seed image gets the version build args, the dashboard gates the update action on manage_settings and stops polling a backend that answers 404, and revived timestamps are typed as Date 2026-09-03 05:04:30 -07:00
Matthew Meszaros b2ea1f1961 feat: add self-hosted update awareness and one-click updates: every binary is stamped with its version and commit, the backend polls GitHub Releases and a new host-side updater (cmd/updater, compose profile or systemd unit) reports the checkout's commit distance, the admin panel's top bar shows a version pill that turns into an update indicator and opens a dialog with confirmation, live step progress and log, restart tracking and result, the dashboard header shows the same pill to every member of a self-hosted instance with the full update flow for platform admins, Setup and health gains update_available and updater_unreachable checks, warmblyctl status prints the version, make upgrade and scripts/upgrade-bare-metal.sh cover the by-hand paths, and docs gain an Updates page plus configuration, health, deployment and API reference updates 2026-09-03 05:04:30 -07:00
Matthew Meszaros 31dabea0a4 feat: rebuild the click-without-open fix on top of the per-link click attribution from #298: a person's click now also counts as an open and a burst that withdraws the click withdraws the open it implied unless a real open is on record, routing readers ignore machine opens as the docs promised, every open gets its own log row and every open and click records the mail client or proxy, browser, device, OS, country, region and city (migration 000124: origin columns on email_link_clicks plus an email_opens table), the tracking service publishes only the address's network in a nullable client_ip field which the consumer resolves with GeoLite and drops, the contact Activity tab shows each open and the origin of opens and clicks, the campaign overview gains a who-engaged-from-where breakdown exposed as engagement in campaign analytics, live open and click events carry occurred_at, client and location, the leads table explains why an open is not always counted, both logs are pruned daily after a year, email_opens joins the export registry, the consumer reads GEODB_PATH optionally, and the guides and API references are updated (fixes #294) 2026-09-03 03:49:44 -07:00
Matthew Meszaros 579b0ac04c feat: merge main into the unsubscribe branch again, carrying the UTM campaign columns alongside unsubscribe_mode in every campaign scanner, keeping unsubscribe links out of the moved link tracker and its UTM tagging, and renumbering the opt-out migration to 000124 because main released 000123 for link clicks 2026-09-03 03:43:47 -07:00
Matthew Meszaros 78c4021053 Merge remote-tracking branch 'origin/main' into feat/link-click-attribution
# Conflicts:
#	docs/content/docs/api/reference/campaigns.mdx
#	docs/content/docs/guides/campaigns.mdx
#	internal/repository/pg_campaign.go
#	internal/repository/pg_campaign_lifecycle.go
#	web/src/components/app/campaigns/NewCampaignDialog.tsx
2026-09-03 03:14:19 -07:00
Matthew Meszaros 64f2d1637b feat: address the review on the unsubscribe PR: register the suppression family in warmblyctl, require the confirm field on the browser unsubscribe POST and cap its body, render a chosen A/B variant through the template engine so its merge fields and unsubscribe link resolve, fold curly apostrophes before opt-out phrase matching, write pasted suppression lists in one transaction, clamp copy by runes instead of bytes, add the constraints NOT VALID plus a lower(email) index in migration 000123, scope the unsubscribe link type-ahead to email bodies, and document DELETE /suppressions/:id 2026-09-03 02:20:47 -07:00
Matthew Meszaros 9485503e03 feat: merge main into the unsubscribe branch, keeping the new campaigns.kind column alongside unsubscribe_mode in every campaign scanner and renumbering the opt-out migration to 000123 because main took 000122 for campaign kind 2026-09-03 01:56:38 -07:00
Matthew Meszaros 5eb92c601c feat: give every campaign email a working opt-out: a reply-to-opt-out line by default or an unsubscribe link (workspace setting under Settings > Sending with a per-campaign override and a {{.UnsubscribeLink}} variable), signed per-recipient unsubscribe links served on the API origin so the List-Unsubscribe header no longer points at a dead warmbly.com page, a confirm page on GET with RFC 8058 one-click on POST and a resubscribe button, reply opt-out detection through the whole-word compliance lexicon with quoted history stripped, a first-class suppression list (Contacts tab, GET/POST/DELETE /suppressions with address and domain entries, audited removal, contact drawer action), the contact Subscribed flag enforced in campaign routing, migration 000122 with a shared recipient_suppressed() predicate, and docs for all of it 2026-09-03 01:52:40 -07:00
Matthew Meszaros 1cb2b65cd7 Merge remote-tracking branch 'origin/main' into feat/link-click-attribution 2026-09-03 01:42:44 -07:00
Matthew Meszaros 6b1ddd8bb8 feat: address the CodeRabbit review on the one-time email preset: rate-limit POST /campaigns-estimate as a read, count a mailbox whose sent-today counter fails as having nothing left today instead of untouched, lock the campaign row FOR UPDATE before counting email steps so concurrent inserts cannot give a one-time campaign two messages, keep a plain-text test email free of the HTML signature, and make the wizard's estimate panel say an audience beyond the two-year horizon cannot be projected instead of reading the null as one sending day 2026-09-03 01:42:00 -07:00
Matthew Meszaros 24b7e26fbb feat: add the one-time email campaign preset from #288: campaigns.kind column with sequence and one_time values and a kind field on POST /campaigns, GET /campaigns?kind= and an one_time count on /campaigns-overview, a POST /campaigns-estimate endpoint projecting deduplicated segment recipients against the mailbox pool's per-day capacity and finish date, a wizard type picker whose one-time flow runs Basics, Email, Audience, Sending and Send with send-now or scheduled start and the estimate panel and then creates, links segments and starts the campaign, a guard refusing a second email step on a one-time campaign, draft/scheduled/sending/sent wording with a One-time badge and Type filter in the campaigns list, kind on the AI create_campaign_draft tool, honouring text_only in the send and test-email paths so plain-text campaigns ship no HTML part or tracking, and the campaigns, segments, API reference and endpoint docs 2026-09-03 01:42:00 -07:00
Matthew Meszaros 7aa19a9227 feat: stop the campaign detail tabs triple-padding phones: the Leads tab renders its full-bleed contacts page without the extra 20px wrapper and the other tabs use a 12px gutter below sm, the campaign name truncates with min-w-0 so its status and One-time pills wrap under it instead of pushing off screen, stat strip cells lose vertical padding on phones, and the templates body, API key meta row, delivery hours grid and the AI variable dialog's 300px sample pane collapse or stack below sm/md 2026-09-03 01:42:00 -07:00
Matthew Meszaros b785d3b86b feat: address review on per-link click attribution: read bare href values and ignore data-href when tracking anchors, compare the destination host instead of substring-matching the tracking domain, tag bare URLs in plain-text bodies when UTM tagging is on, count UTM limits in characters, expose the UTM overrides in the AI campaign tool, dedupe clicks by ticket so two links sharing a destination are two clicks, count machine_clicks only for steps with machine and no human clicks, never withdraw a click stamp that predates per-link logging, keep the coarse timeline click unless a logged click stands for it, resolve the link once per event, give auto-clicks their own tooltip and make the wizard's discard guard notice toggled settings 2026-09-03 01:20:48 -07:00
Matthew Meszaros 117affbb09 Merge remote-tracking branch 'origin/main' into fix/profile-avatar-persistence 2026-09-03 01:20:48 -07:00