Matthew Meszaros
|
9356c748b9
|
Merge remote-tracking branch 'origin/main' into fix/main-ci-failure-and-issue-400
# Conflicts:
# docs/content/docs/guides/mailboxes.mdx
# site/public/install.sh.sha256
|
2026-09-09 08:59:37 -07:00 |
|
Matthew Meszaros
|
18a8c7b009
|
feat: give a self-hosted instance on-demand TLS for customer tracking and forms domains, gating Caddy's ask on a /tls/authorize endpoint that answers only for domains this instance has verified, so a workspace CNAME stops serving every tracked link and opt-out link with no certificate
|
2026-09-09 08:56:08 -07:00 |
|
Matthew Meszaros
|
494996ea8a
|
feat: stop the backend image copying scripts/install-worker.sh, which the pull-based fleet change deleted, and clear the rest of that removal's dangling references out of the backend systemd unit, deploy/README, architecture and troubleshooting docs
|
2026-09-09 08:56:05 -07:00 |
|
Matthew Meszaros
|
6ebf9cfdcf
|
Merge remote-tracking branch 'origin/main' into fix/self-hosted-unsubscribe-domain
|
2026-09-09 08:24:04 -07:00 |
|
Matthew Meszaros
|
a8d0e80f68
|
feat: address the CodeRabbit review: render EMAIL_BRAND_NAME in every transactional template body and subject instead of a hardcoded product name, name FRONTEND_BASE_URL as the source when emailed links come from it, warn on an insecure effective dashboard URL even when APP_URL was inferred rather than configured, and correct the unsubscribe guide's claim that an unset API_PUBLIC_URL disables link minting
|
2026-09-09 06:52:37 -07:00 |
|
Matthew Meszaros
|
7d58b874b8
|
feat: keep every recipient-facing and self-host-facing address on the deployment's own domain: mint unsubscribe links on a workspace's verified tracking domain (served by the tracking service, proxied to the backend that owns the pages), attach RFC 8058 one-click only over https, resolve all branding through config.Brand() gated on SelfHosted() so a self-host's email footer, sign-in links, stats card, API example and public form badge name nobody else, drop the app.warmbly.com fallback from AppBaseURL, blank TRACKING_DOMAIN and FORMS_DOMAIN on core-only installs, and have install.sh offer to configure a fresh interactive install instead of silently defaulting to localhost
|
2026-09-09 06:34:43 -07:00 |
|
Matthew Meszaros
|
8b83062eff
|
feat: fix ten defects the review found, the worst being that every seeder still wrote the worker columns migration 000141 moved to fleet_nodes so make dev could not seed, that the generated systemd unit used a command substitution systemd never expands so a joined machine restart-looped while the script reported success, that the state directory was not bind-mounted so the node wrote its update target inside the container where the host timer never saw it, and that placement stopped assigning warmup pool membership so paid mailboxes silently warmed in the free pool
|
2026-09-09 05:21:17 -07:00 |
|
Matthew Meszaros
|
704fecdcc0
|
feat: settle on WARMBLY_NODE_REGION as the one region variable for every role, because the join script wrote it while the worker still read WORKER_REGION, so a joined worker reported no region and the placement geography term stayed dead for exactly the machines that had been given one
|
2026-09-09 05:04:55 -07:00 |
|
Matthew Meszaros
|
435dbb522f
|
feat: replace the worker tier/type/risk-pool/egress categories with a scored placement model and make the fleet pull-based, so a machine joins with one command, workers and consumers share one node registry with usage and liveness, nodes self-update to the version the control plane resolves, and the Hetzner provisioning, worker profiles and SSH orchestrator are removed
|
2026-09-09 04:54:01 -07:00 |
|
Matthew Meszaros
|
9ce1e060dd
|
feat: fix the campaign step body editor for issue #380 by registering TipTap's UndoRedo and HardBreak so Ctrl+Z and Shift+Enter work at all, normalising pasted HTML from Gmail/Outlook/Word so their blank-line scaffolding stops rendering a second gap on top of our paragraph margins, adding a workspace email image library (migration 000139, public email-images/ objects sharing the attachment storage quota, keyset-paginated /email-images endpoints) with toolbar upload, URL and library insertion, drag-drop, clipboard paste and a size/alignment/alt bubble, and adding an HTML source view that names the tags the schema would drop before switching back
|
2026-09-08 21:00:09 -07:00 |
|
Matthew Meszaros
|
c26300ae5e
|
feat: backend half of the admin panel upgrade: delete the unrouted provisioning, releases, plan, discount and enterprise-inquiry admin handlers with their service and repository methods, retire the six admin permission bits nothing gated as reserved placeholders so live bit positions and existing masks are unchanged and IsSuperAdmin checks the live set, add forty admin endpoints for mailbox sync governor state with clear-throttle and restart-backfill, in-flight send reservations, cross-workspace dead letters with replay, task failures, webhook delivery health with reclaim, fleet capacity, the control loops decision log, dedicated bindings with release and the routed convert-to-dedicated, operator-driven workspace export and import, per-organization API keys and webhooks, warmup invalid-token abuse and action history, and signups by acquisition channel, and add a scheduled_job_runs table (migration 000135) with a jobrun package that every backend and consumer loop now records through and a run-now request the owning process picks up within fifteen seconds
|
2026-09-07 21:40:38 -07:00 |
|
Matthew Meszaros
|
769a05aa90
|
feat: identify an IMAP folder by its name rather than by its UIDVALIDITY, which RFC 3501 never promised was unique across folders, so a mailbox on a server that stamps that number with the folder's creation time no longer loses the entire sync of every folder in a tree created in the same second, with the folder row keyed on (email_id, mailbox), each stored message stamped with its folder's name alongside the UIDVALIDITY generation its uid belongs to, a rename followed as a move that carries the mail and the cursor instead of orphaning both, and a changed UIDVALIDITY treated as what it is, the cursor going void
|
2026-09-07 08:21:17 -07:00 |
|
Matthew Meszaros
|
04dc83da38
|
Merge the Sentry review fixes into the PostHog branch, keeping the new JavaScript escaping for the PostHog runtime-config values too
|
2026-09-07 04:48:30 -07:00 |
|
Matthew Meszaros
|
f043bfc761
|
feat: address the CodeRabbit review on the Sentry PR by escaping every runtime-config value the web and admin entrypoints write into a JavaScript string literal so a quote or newline in a DSN cannot inject or discard the whole config, reporting only the count and types of a failed query's parameters instead of their values now that the db scope actually applies, cloning the fallback hub in errs.Hub so scope cannot leak between requests, flushing the tracking service before every fatal exit since process::exit skips the guard's drop, treating a blank WARMBLY_RELEASE or APP_ENV as unset in realtime, stamping the environment and a service tag into the forms page, and correcting the docs claim that an unset DSN means the SDK is never initialised
|
2026-09-07 04:47:51 -07:00 |
|
Matthew Meszaros
|
d00960ad59
|
Merge feat/sentry-everywhere (with main) into the PostHog branch
|
2026-09-07 04:26:17 -07:00 |
|
Matthew Meszaros
|
be16f77f5d
|
Merge main into the Sentry branch
|
2026-09-07 04:25:41 -07:00 |
|
Matthew Meszaros
|
b61a3588b6
|
Merge feat/sentry-everywhere into the PostHog branch to keep the stack current
|
2026-09-07 04:24:31 -07:00 |
|
Matthew Meszaros
|
b1a1941574
|
feat: flush the SDK before exiting on a captured boot failure via a new errs.CaptureFatal, because the previous capture-then-log.Fatal pattern in the backend and consumer mains killed the background sender before it had sent anything, making a failure to boot the one error that never reached Sentry
|
2026-09-07 04:24:30 -07:00 |
|
Matthew Meszaros
|
8e9f67f46b
|
feat: add cookieless PostHog analytics for the hosted marketing site and dashboard with server-side signup and subscription events, a first-party acquisition record written once at signup on a new organization_acquisition table registered in the org-transfer spec, an acquisition column and channel filter in the admin org list, and never a single request from a self-host because every key is unset by default
|
2026-09-07 04:18:33 -07:00 |
|
Matthew Meszaros
|
51dedc90ee
|
feat: put every runtime behind one optional error-reporting story: a single internal/observability/errs wrapper that is now the only package importing sentry-go, InitSentry for cmd/forms, release and environment tags on every service from the existing build stamp, optional Sentry in the admin panel and the public forms app, the sentry crate in the Rust tracking service, release tagging in realtime, CI source-map upload that only runs when a Sentry token is configured, and docs covering the DSN for each service
|
2026-09-07 03:51:06 -07:00 |
|
Matthew Meszaros
|
63d6637d0c
|
feat: expose forms, segments and the suppression list to agents by adding twenty-three tools to the shared registry, so an MCP client, a Hermes-style REST agent, the dashboard assistant and warmblyctl can all build an audience, publish a lead-capture form, link one to a campaign and manage who is unreachable, each gated on the same contact and campaign permissions its HTTP route already uses
|
2026-09-07 03:39:42 -07:00 |
|
Matthew Meszaros
|
5d60fe9a21
|
feat: fix campaign restart dead end (issue #340): resolve preflight tracking senders through the same explicit/tags/all pool the scheduler uses so a connected mailbox is never reported missing, fetch the launch dialog's step count from the steps endpoint instead of a field the campaign API never returns, turn on Keep running for new leads when a form or an automation feeds a campaign (migration 000131 backfills existing ones) and when a member starts a campaign whose every lead has finished so it goes active and waits for leads instead of answering 400, return waiting_for_leads from the start endpoint for the dialog's success screen, and document the no_leads and no_remaining_leads codes and the new behaviour in the campaigns, forms, automations and API docs
|
2026-09-07 02:26:50 -07:00 |
|
Matthew Meszaros
|
7b11489c85
|
feat: keep a campaign running for new leads (issue #336): add a continuous campaign setting (migration 000130, on by default once a segment is linked) under which a campaign that runs out of leads stays active and idle with idle_since set instead of finishing, is startable with no leads, is re-checked by the reconciler every pass and shows as waiting for leads in the dashboard list, header and live activity panel with a CAMPAIGN_IDLE realtime event; make WakeCampaigns restart a finished campaign through the full launch checks so a lead added by the contacts API, a bulk update, an import or the add-to-campaign automation action reopens it exactly like a linked segment does, and write a refused restart to the campaign activity log once an hour per reason instead of only the backend log; expose continuous on the campaign API and preferences page and document it in the campaigns, segments, forms, automations, API reference and realtime docs
|
2026-09-06 02:50:16 -07:00 |
|
Matthew Meszaros
|
a3989f9d9c
|
feat: turn automations into a lead-intake path so a Facebook, Instagram, LinkedIn or TikTok lead form pushed by Zapier, Make, n8n or any webhook becomes a tagged, campaign-enrolled contact without leaving Warmbly: add the warmbly.upsert_contact and warmbly.add_to_campaign built-in actions with templated field mapping, custom fields, tags, campaign and an if-exists policy, fire a rich contact.created event from the one contact write path (silent for file imports, sheet syncs and API batches over 100) and expose contact.created and form.submitted as automation triggers with condition fields, variables and sample data, carry an automation depth through events raised by an action so a flow that creates a contact cannot re-trigger itself past five hops, stamp automation-created contacts with the new automation source (migration 000129), share the campaign picker between the sheet sync wizard and the automation builder, document lead intake in the automations, Zapier, Make, contacts, forms, integrations, expressions and webhook pages plus a new n8n guide, mirror the new triggers and actions on the marketing automations page, and drop the 34 MB cli binary that was committed by mistake
|
2026-09-06 00:36:05 -07:00 |
|
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
|
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
|
d80816efa1
|
feat: make a successful warmbly auth login always the active host instead of deciding it from three overlapping conditions, and say why the CLI state file tolerates an unwritable home
|
2026-09-04 20:16:56 -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
|
f46fc86a7c
|
feat: add the warmbly CLI at cmd/cli, a signed-in multi-host client of the public REST API with 30 command families over one spec table, gh-shaped api passthrough, live event tailing, tables on a terminal and JSON in a pipe, and a confirmation gate in front of everything that puts real mail on the wire
|
2026-09-04 20:14:33 -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
|
5fd9aedb80
|
Merge remote-tracking branch 'origin/main' into feature/plan-upgrade-paywall-modal
|
2026-09-04 09:11:51 -07:00 |
|
Matthew Meszaros
|
e5360713e7
|
Merge remote-tracking branch 'origin/main' into feature/install-script-setup-wizard
|
2026-09-04 06:43:12 -07:00 |
|
Matthew Meszaros
|
77058a2cb4
|
feat: address the review on the installer branch by keeping the database password out of pg_dump's argv, excluding backup bundles from the blob root they are written into, tolerating blobs that change or vanish mid-archive, making the instance-settings bootstrap a single atomic insert, and validating the release tag before it is written into .env
|
2026-09-04 06:24:05 -07:00 |
|
Matthew Meszaros
|
fb868dfe40
|
feat: delete the storage objects of a step's attachments when the step is deleted, since the attachment rows cascade away with the sequence and left their bytes counted against the organization's storage quota with no row left to reach them
|
2026-09-04 06:20:26 -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
|
42c3000383
|
feat: verify the backup bundle's recorded dump digest before warmblyctl restore empties the target schema, so a truncated bundle is refused instead of leaving the instance with neither its own data nor the bundle's
|
2026-09-04 05:56:12 -07:00 |
|
Matthew Meszaros
|
d68bbcd2ab
|
feat: add a one-command self-host installer at warmbly.com/install.sh with an interactive data-control wizard, give docker-compose.yml image keys and per-store volume variables, add an image-mode updater, move engagement/form/audit retention into instance settings, and add warmblyctl backup/restore
|
2026-09-04 05:49:54 -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
|
701dc404a2
|
Merge remote-tracking branch 'origin/main' into fix/click-without-open-tracking
|
2026-09-03 05:22:05 -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
|
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
|
99be92f159
|
feat: attribute every campaign click to the exact link with a per-link click log (email_link_clicks) behind the contact activity timeline, campaign live feed, recent activity and the email_clicked webhook, add per-campaign automatic UTM tagging (utm_tracking with source, medium and campaign overrides, utm_content from the link text) applied at send time to the stored ticket destination, and classify opens and clicks as machine when they arrive within ten seconds of dispatch or when several links of one email are followed within five seconds, so scanner clicks are logged but never count as engagement, fire automations or send webhooks
|
2026-09-03 01:05:34 -07:00 |
|
Matthew Meszaros
|
290f1f00aa
|
Merge remote-tracking branch 'origin/main' into feature/public-forms
# Conflicts:
# cmd/backend/main.go
# docs/content/docs/guides/workspace-export-import.mdx
|
2026-09-01 09:29:53 -07:00 |
|
Matthew Meszaros
|
05d74dbe9f
|
feat: forms v2, a full redesign of the hosted form and its builder: eight one-click themes, card/wide/split layouts with a brand cover panel, classic paged and Typeform-style focus modes split on a new page_break block, logo/cover/background uploads with size, fit and veil controls, an optional header bar that can span the page or sit with the form, a real HSV colour picker and font preview, per-contact personalized links that prefill and attribute without email verification, a render-token gate so the form JSON cannot be scraped without loading the page, funnel analytics with per-page drop-off and identified visitors, a leads-style forms list and responses table, and an organization custom forms domain verified by CNAME with hourly re-checks
|
2026-09-01 09:27:28 -07:00 |
|