459 Commits
Author SHA1 Message Date
Matthew Meszaros 817233cc09 feat: record one unresolved row per mailbox error code instead of one per sync pass, and clear IMAP_UNKNOWN when a pass completes (issue #405) 2026-09-09 09:30:17 -07:00
Matthew Meszaros cab27fac62 Merge remote-tracking branch 'origin/main' into fix/main-ci-failure-and-issue-400 2026-09-09 09:05:45 -07: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 3591d64404 Merge remote-tracking branch 'origin/main' into fix/issue-401 2026-09-09 08:58:08 -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 16261de4cb Merge remote-tracking branch 'origin/main' into fix/issue-401 2026-09-09 08:51:02 -07:00
Matthew Meszaros 68b5d8f358 feat: bind a campaign lead to the mailbox that sends its first email so every follow-up leaves from the same address, holding a lead back while its mailbox is merely out of budget or outside its hours and moving it to another mailbox only when that one can no longer send for the campaign at all 2026-09-09 08:51:00 -07:00
Matthew Meszaros 3153f9ff23 Merge remote-tracking branch 'origin/main' into fix/self-hosted-unsubscribe-domain 2026-09-09 08:44:13 -07:00
Matthew Meszaros b18bbcad7a feat: name API_HOST as the listen address the unsubscribe link falls back to when API_PUBLIC_URL is unset, rather than describing the resulting localhost address alone 2026-09-09 08:34:04 -07:00
Matthew Meszaros 035865e5c5 Merge remote-tracking branch 'origin/main' into feat/issue-393-html-css-email 2026-09-09 08:30:52 -07:00
Matthew Meszaros f5eeac7b2a feat: address the CodeRabbit review by carrying every stretch of stylesheet the CSS parser cannot read through as a verbatim item, since the sheet is rewritten from parsed items the moment any rule inlines and an unrelated match was deleting the rest, keeping a link's destination out of the content score now that the text renderer emits it so a CTA pointing at a free-trial page stops costing eight points, promising inlining in the editor only for a stylesheet that is actually eligible for it, skipping the client notes entirely for a plain-text campaign that ships no HTML part, switching a step into HTML mode when a template replaces its body with document markup rather than waiting for the next visual edit to gut it, accepting a pasted background shorthand only when it is a single colour so Word's "yellow none repeat scroll" stops becoming an invalid longhand, listing in SCHEMA_TAGS only the tags the mounted schema actually keeps so the warning fires for h1, font, center, thead and caption instead of staying silent while they are dropped, and correcting the guide's byte-for-byte claim and its unconditional plain-text claim 2026-09-09 08:30:49 -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 a96a44d916 Merge remote-tracking branch 'origin/main' into refactor/automatic-worker-mailbox-placement 2026-09-09 07:19:03 -07:00
Matthew Meszaros cdbdad09ee feat: stop the unibox conversation list resetting to the top when an email is opened, by keying the app shell's route boundary and Suspense on route identity rather than the raw pathname (the open thread is a path segment, so every click was remounting the whole page), remembering each list's scroll offset across remounts and the mobile display:none pane, patching mark-as-read into the query cache instead of refetching every loaded page, and auto-loading the next page at the end of the list (issue #396) 2026-09-09 06:52:57 -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 2c1b5d8204 Merge remote-tracking branch 'origin/main' into feat/issue-393-html-css-email 2026-09-09 06:35:44 -07:00
Matthew Meszaros 426ea45013 feat: full HTML and CSS support for campaign bodies and mailbox signatures, adding a send-time CSS inliner and a structure-aware plain-text renderer in internal/pkg/mailhtml, persisting HTML mode on a step through the previously unused sequences.body_code so a designed email is no longer gutted by the editor schema on reopen, widening the TipTap schema to hold tables, div containers, colours, fonts, sizes and alignment with toolbar controls for each, folding alignment into a single style producer because TipTap merges two style attributes by splitting on the first colon and truncated background-image url(https://...) to url("https"), replacing the regex plain-text stripper that put a whole stylesheet into the text/plain part and cost a content score eight points for a .free-trial-banner class, locating </body> past Outlook conditional comments so the signature and opt-out footer stop landing inside one, forcing the signature source view for markup a contentEditable cannot host safely, and reporting per-client compatibility findings from the preview endpoint 2026-09-09 06:34:47 -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 75a068cd07 feat: always create and mount BLOB_FS_ROOT for a node because its storage layer exits when the directory is not writable and an unmounted path is root-owned by docker, and say plainly in the script and the docs that filesystem blobs cannot serve a fleet at all since the worker has to read the body the backend wrote 2026-09-09 06:06:04 -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 58b7d81d33 Merge remote-tracking branch 'origin/main' into refactor/automatic-worker-mailbox-placement 2026-09-09 04:54:04 -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 db2a9ba439 feat: stamp the rotating campaign send's real sending mailbox onto tasks.email_account_id before dispatch so budgets, round-robin position, deliverability rates and the contact feed name the mailbox that actually sent, and stop the contact email list and campaign-state steps reporting automated prefetches as recipient opens (issue #392) 2026-09-09 03:55:28 -07:00
Matthew Meszaros 1e1bb3622e feat: address the CodeRabbit review on the email editor PR by generating email image object keys from a uuid and the sniffed extension instead of the uploader's filename so a name carrying '..' cannot produce a key the public route refuses to serve and a private filename never travels inside a recipient's URL, deleting the stored object before its row and refusing the request when storage will not take it so a failed delete leaves nothing loadable that the quota has stopped counting, accepting only https image addresses, resynchronising the HTML source view when a template replaces the body underneath it, and rendering the campaign preview through the inbox's sandboxed frame so markup written in the source view can neither run nor restyle the dashboard for a teammate 2026-09-08 21:17:36 -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 0e7525866a feat: merge main into the select-all branch and rebuild the contact selection on top of its new keyset pagination, dropping this branch's own cursor repair now that Search carries the sort key and boundary value in a SortCursor, re-extracting the shared WHERE builder from that version, and pointing SearchIDs at the contactSorts spec and the conditional campaign-count lateral so a resolved selection orders and joins exactly the way the list does 2026-09-08 20:26:34 -07:00
Matthew Meszaros c7a44ad909 feat: complete the CRM push 400 description in the OpenAPI spec with the shared resolver's own failures, a select-all carrying no filters and a filter matching more than fifty thousand contacts, so the published contract matches every way the endpoint can refuse a selection 2026-09-08 20:02:26 -07:00
Matthew Meszaros 1432e7c459 feat: publish the exclusion-list bound that ResolveSelection enforces by giving every OpenAPI exclude field maxItems 50000 and naming too_many_contacts in the bulk update, bulk delete and CRM push 400 descriptions, and note the cap on the selection table in the contacts API reference 2026-09-08 19:50:46 -07:00
Matthew Meszaros c8042e2942 Merge remote-tracking branch 'origin/main' into feat/campaign-entry-delay 2026-09-08 09:58:38 -07:00
Matthew Meszaros 37ed967919 Merge remote-tracking branch 'origin/main' into fix/issue-382-user-experience 2026-09-08 08:32:31 -07:00
Matthew Meszaros 4e52e9476b Merge remote-tracking branch 'origin/main' into feat/campaign-entry-delay 2026-09-08 07:31:31 -07:00
Matthew Meszaros f8c0899be4 feat: address the CodeRabbit review by promoting a rounded wait into the next unit so a contact drawer never reads 60 minutes or 24 hours, describing the entry delay's anchor as the lead's own enrolment moment with the campaign's creation date only as the legacy fallback in both the campaigns and segments guides, replacing the launch dialog's 'sending begins immediately' line when a delay is set, following an outside value change with the picker's preset or custom mode, and chaining the trigger card's saves so two commits inside one round trip cannot land out of order 2026-09-08 07:30:24 -07:00
Matthew Meszaros 0f83329936 feat: answer the CodeRabbit re-review by guaranteeing the promise the failed-pin warning makes: an import-level note now goes to the front of the error list and is never dropped by the per-row cap, so a file full of bad addresses cannot bury or evict the one note explaining why the rows that did import are not in the segment they were imported into, with a single note however many segments were targeted and a live test that forces the write to fail behind a capped error list 2026-09-08 06:54:36 -07:00
Matthew Meszaros 177e36265d Merge remote-tracking branch 'origin/main' into fix/issue-381-user-experience 2026-09-08 06:47:37 -07:00
Matthew Meszaros a453573c7b feat: finish the OpenAPI half of the contact selection change by rewriting the bulk delete, bulk update and CRM push operation and 400 descriptions for the filter form and its selection_too_large and empty-match failures, and encoding the two valid bodies as a oneOf so a generated client cannot send an empty object, a select-all with no filters, or an id list and a filter at once 2026-09-08 06:17:36 -07:00
Matthew Meszaros f46728733e feat: address the CodeRabbit review on the select-all PR by tying the header checkbox to the rows on screen and making it put unticked rows back before it clears a select-all, bounding the exclusion list before it sizes a map, skipping half-filled custom-field pills when a filter is saved as a segment so the segment matches the search, and naming the fifty-thousand selection cap on the campaigns and segments guides 2026-09-08 05:39:16 -07:00
Matthew Meszaros c59446271b Merge remote-tracking branch 'origin/main' into feature/bulk-select-all 2026-09-08 05:20:02 -07:00
Matthew Meszaros a6862477b3 feat: add a 'select all matching' selection to every contact list so a bulk action can cover the whole filtered set instead of only the loaded pages: a ContactSelection body (all + filters + exclude) on bulk update, bulk delete, verification, batch research, segment members and CRM push resolved server-side through a new SearchIDs that shares the search's WHERE builder, a Gmail-style banner and shared selection module in the contacts table, campaign Leads and segment members views, an uncapped select-all in the From contacts picker, and fixes for the keyset cursor comparing a row against itself (contact pagination repeated and skipped rows past page one) and the dashboard sending custom-field filters under the wrong wire name 2026-09-08 05:19:59 -07:00
Matthew Meszaros 846d691768 feat: write the campaign's 'no step is due yet' activity line once a day instead of on every deferred wake-up, and name the entry delay in it when the campaign holds its first emails, so launching a delayed campaign explains itself instead of filling the feed with two hundred identical lines 2026-09-08 05:09:37 -07:00
Matthew Meszaros ed50ad9f2f feat: add a campaign entry delay so a contact's first email can wait a set time after they enter the campaign, with campaigns.entry_delay_minutes and a campaign_leads.added_at anchor (migration 000136), the delay applied in the router's per-lead due check and floored into the placer through ContactSequencePair.NotBefore, a distinct entry_delay constraint in the contact next-action preview, and the control surfaced on the Schedule tab, a new Trigger card at the top of the Steps canvas, the campaign wizard's Schedule step, the launch dialog, the update_campaign AI tool and the iOS schedule page, plus guides, API reference and live scheduler and repository tests 2026-09-08 05:06:04 -07:00
Matthew Meszaros 55894a0e9d feat: fix the campaign Leads tab and contact export stopping partway through a large list by rebuilding the contacts keyset cursor: the boundary subquery named the outer row's alias so Postgres read it as a correlated self-reference and the whole comparison collapsed to c.id >= <cursor uuid>, serving each page the newest rows of a randomly shrinking id range until it ran out at a third of the leads, so the opaque token now carries the ordering it was taken under plus the boundary row's own sort value and id, rejects a token replayed under another sort, and pairs with a direction-following id tiebreak, an ORDER BY that names its NULL placement, a new (organization_id, created_at DESC, id DESC) index, EXISTS campaign and category filters and a lateral campaign_count the query only joins when a filter or sort asks for it, taking a page of a 50k-contact organization from 120ms to 3ms, and the dashboard now keeps the rows it already loaded when a later page fails and says how far through the list Load more is 2026-09-08 05:01:15 -07:00
Matthew Meszaros b5fc5b2974 feat: document importing into a segment: the file wizard and a Google Sheets source both inherit the segment they were started from, a condition-less segment is the shortest way to turn a file into an audience, and the API reference gains segment_ids on lead-sync create and update, the ?segment_id= list filter and segments_pinned on the import result 2026-09-08 04:59:31 -07:00
Matthew Meszaros 800c4a062f feat: frontend half of the admin panel upgrade: drop ten unused dependencies, the dead worker-load and plan requests, the stub pages and the retired permission names, fix the analytics client that sent the wrong query parameters and the mail test toasts that used an unmounted toaster, add a Cmd-K command palette with live user, organization, mailbox and worker search, a mobile nav drawer, document titles, a Sentry-wired route error boundary and permission gates on every route, replace polling with the realtime spine wherever an event exists, fold Analytics into Overview with a signups-by-channel card, merge System Status into Setup and health and Settings, Notifications and Effective limits into Configuration as tabs with redirects from every old path, add the Sync, Sends, Jobs, Fleet, Admins and Transfers pages plus API-key, webhook and transfer tabs on the organization page, mailbox reassignment on the worker page and abuse and action-history tabs on Warmup, and add the admin-panel docs page with every nav reference updated 2026-09-07 21:40:38 -07:00
Matthew Meszaros 156a90c39a feat: default every PostHog capture host (site build, dashboard runtime, backend analytics client) to PostHog Cloud US instead of EU, and update the configuration docs table, since the customers are primarily US-based 2026-09-07 20:28:31 -07:00
Matthew Meszaros de4b1e3aff Merge remote-tracking branch 'origin/main' into fix/issue-371-public-images
# Conflicts:
#	site/public/install.sh.sha256
2026-09-07 09:13:22 -07:00
Matthew Meszaros 6542047745 Merge remote-tracking branch 'origin/main' into fix/issue-371-public-images 2026-09-07 09:07:22 -07:00
Matthew Meszaros 25484f70ab Merge remote-tracking branch 'origin/main' into feat/issue-357-loopback-mail-relays 2026-09-07 09:05:11 -07:00
Matthew Meszaros c4bfbaf4a9 feat: address the review on the image publicity gate by passing build-push only the seven services that workflow actually publishes, since web, admin and cli have no :dev tag and would have warned falsely on every push to main, by taking the release tag through the step env instead of interpolating github.ref_name into the shell, and by no longer asserting the tag is fine when a pull is refused, because GHCR denies an unknown namespace exactly as it denies a private one, so a mistyped --registry now gets its own reading in the installer message, the troubleshooting table and the fork note, which also splits the personal and organization paths to the visibility setting 2026-09-07 09:02:23 -07:00
Matthew Meszaros 719a81866c feat: gate the release on every published image being pullable with no credentials, because GHCR creates each package private and does not inherit the repository's visibility, so the authenticated imagetools inspect in create-release passed for four releases while ghcr.io/warmbly/warmbly/* returned unauthorized to everyone outside the org and no curl | sh self-host install could pull a byte (#371); adds scripts/check-images-public.sh as the anonymous pull test, makes it the gate before create-release and the source of the digests in images.json, warns from build-push on main where a new service image first appears, and teaches install.sh to report a registry refusal as one instead of as a missing tag 2026-09-07 08:51:57 -07:00