Commit Graph
270 Commits
Author SHA1 Message Date
Matthew Meszaros 47defafa09 feat: fix the six self-host defects reported in issue #439 (#456)
* feat: fix the six defects reported in issue #439 by mapping the IMAP UNAVAILABLE, INUSE and NONEXISTENT response codes to retry-level errors instead of a critical reconnect prompt, synthesising a stable no-msgid key so one message with no Message-ID header can no longer 400 the internal map endpoint and wedge every later sync pass with its cursors held, adding mailhtml.FromText and HasContent so an API or agent-created step with a plain body stops shipping the composer's empty div placeholder as its text/html part (derived on create and plain-only update, exposed as body_html on update_campaign_step, dropped at send and preview time, and refused at campaign start with empty_step_body), honouring sender_strategy='explicit' in ResolveCampaignSenderPool and ValidateCampaignReady so an emptied explicit pool parks the campaign instead of widening it to every mailbox in the workspace, making the paused_no_accounts auto-pause loud with an error log line, an error-level activity-feed entry and an org-scoped CAMPAIGN_PAUSED realtime pulse, gating the admin sign-in's Turnstile widget on GET /v1/auth/config so a self-host with CAPTCHA_PROVIDER=none is not locked out, and parsing NATS_URL down to its host:port so a credentialed bus URL no longer reports NATS down

* feat: act on the self-review of the issue #439 fixes by dropping the campaign wizard's own escapeHtml body_html builder, which entity-escaped the quotes in a conditional and made the template fail to parse at send time, and letting the backend's FromText render that part instead so wizard-written steps also get their bare URLs linked for click tracking, correcting the docs and openapi description that claimed an explicit sender pool never falls back when it still unions its tags as migration 000013 designed, extracting the duplicated blank-HTML-part guard into dropBlankHTMLPart shared by the send path and the preview, and recording why the no-msgid key keeps the folder name despite a RENAME changing it

* feat: address the CodeRabbit review on the issue #439 fixes by holding the admin sign-in's Turnstile widget unmounted until /v1/auth/config resolves so an instance with no route to Cloudflare cannot raise a widget error on a screen nobody submitted, failing StartCampaign closed when the sequence read errors rather than skipping both the malformed-template and empty-body refusals, giving TCPCheck the default port its protocol assumes so a portless NATS_URL is no longer reported down, leaving a URL that carries a merge field unanchored because the send path renders bodies with text/template and a quoted contact value would break out of the href, and correcting the sequences guide and the Campaign and CampaignUpdate openapi descriptions that named the wrong tag field
2026-09-12 03:13:38 -07:00
Matthew Meszaros 80c3c79a31 feat: add live coverage that the machine windows survive the settings document's jsonb round trip and reach the classifier, a guard that every shipped scanner CIDR is written as its own network address since the loader truncates host bits silently, and correct the comments that claimed an edit lands on the very next event when the consumer reads through a thirty second cache in its own process 2026-09-11 03:11:35 -07:00
Matthew Meszaros 184a3dc08e feat: make the automated-open and automated-click windows operator-editable under Instance settings and raise their defaults to 60s and 30s, because the ten-second window was anchored on dispatch to the worker rather than on delivery and routinely expired before the recipient-side gateway it was meant to catch had even seen the message, and add Barracuda's published Email Gateway Defense blocks to the scanner catalogue with Proofpoint, Mimecast and Cisco shipped commented out because browser isolation renders a clicked page from the vendor's own network 2026-09-11 02:53:34 -07:00
Matthew Meszaros a84ab48729 Merge branch 'main' into feature/posthog-error-tracking 2026-09-10 10:25:21 -07:00
Matthew Meszaros 2ff350ecf9 feat: apply WARMBLY_POSTHOG_ERROR_TRACKING to the public form pages too, by having cmd/forms stamp an empty browser key when it is false, since the page can only act on whether a key arrived and the flag otherwise silenced the dashboard and the admin panel while leaving form pages reporting 2026-09-10 19:14:34 +02:00
Matthew Meszaros ced741e352 feat: make PostHog the default error tracker across every runtime while keeping Sentry fully supported alongside or instead of it, by turning internal/observability/errs into a two-sink fan-out with a local-log fallback, adding $exception capture to the Go services, the Rust tracking service, the Elixir realtime service and the dashboard, admin and form apps, reporting gin panics with their route, request id, workspace and user, attaching that identity plus a route and failed-request trail to browser exceptions, and wiring POSTHOG_ERROR_TRACKING, the node join env, compose, source-map upload and the docs to match 2026-09-10 19:11:32 +02:00
Matthew Meszaros 331db196d8 feat: locate every content-check issue in the subject or the body with the exact fragments that caused it and a one-line fix, add POST /templates/analyze running the configured LLM over a campaign template for located spam findings quoted verbatim from the copy plus a rewritten subject and an overall score, verify every model quote against the draft so an invented sentence is dropped rather than shown, pin the analysis temperature so re-checking unchanged copy returns the same number, and give the editor panel a Re-check button that re-runs both passes and reports the movement since the last check 2026-09-10 09:28:00 -07:00
Matthew Meszaros 6149f84c7d feat: give warmblyctl the same apikey purge the customer CLI has, so the operator half of the API surface can delete a revoked key too and the two CLIs name the verb the same way 2026-09-10 05:37:28 -07:00
Matthew Meszaros 396bab0e06 Merge branch 'main' into feat/issue-414-delete-api-key 2026-09-10 05:36:06 -07:00
Matthew Meszaros 9e37ea73a8 feat: add a permanent delete for API keys, DELETE /api-keys/:id/permanent plus a Delete key button under a revoked key in the dashboard drawer and warmbly key purge, taking the key's usage logs with it and refusing any key that could still authenticate so revoking stays the step that records why a credential ended (issue #414) 2026-09-10 05:02:05 -07:00
Matthew Meszaros 47ba13083e feat: make a split deployment work end to end by fixing the three defects that made an off-host node impossible to configure (nodeEnvKeys shipped S3_BUCKET and KMS_KEY_ID, which nothing reads, so an AWS-backed node silently used the default bucket and key alias; a joined consumer never received PRIMARY_DB and died at boot; and node.env was rewritten on every join with no file an operator could add to), then removing the need for cloud credentials on a node at all with brokered KMS and blob providers that renderNodeEnv hands out automatically, plus deploy/split-cloud, scripts/aws-bootstrap.sh, two fleet instance checks and the docs 2026-09-10 13:58:59 +02:00
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