Matthew Meszaros
|
88ea60cd4d
|
feat: renumber the custom-domain TLS lookup indexes to 000144 after main landed 000143 for campaign lead senders, because two migrations sharing a version stop golang-migrate before either runs
|
2026-09-09 09:07:26 -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
|
92b811773e
|
feat: fail the build when a Dockerfile COPY names a path that is not in the repository, because build-push.yml runs only on push to main and nothing on a pull request builds an image, so a COPY of a file the same change deleted is green everywhere until it breaks main
|
2026-09-09 09:03:54 -07:00 |
|
Matthew Meszaros
|
128c2261e3
|
Merge pull request #403 from warmbly/fix/issue-401
feat: keep every step of a lead's sequence on one sending mailbox
|
2026-09-09 09:03:50 -07:00 |
|
Matthew Meszaros
|
b74fff34e5
|
feat: prove the on-demand TLS domain lookup against the real schema, covering the campaign-level tracking override and the empty string that every row without a custom domain stores
|
2026-09-09 08:59:40 -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
|
7d94da9a0f
|
Merge pull request #398 from warmbly/fix/self-hosted-unsubscribe-domain
feat: keep every recipient-facing address on the deployment's own domain
|
2026-09-09 08:51:39 -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
|
05847328af
|
Merge pull request #399 from warmbly/feat/issue-393-html-css-email
feat: full HTML and CSS support for the email editor
|
2026-09-09 08:39:23 -07:00 |
|
Matthew Meszaros
|
c403af6757
|
feat: give the recipient opt-out routes their own per-source budget in the tracking service instead of the counter pixels and clicks spend, so a NAT or mail gateway that exhausts the tracking budget cannot make the unsubscribe page answer 429 to the recipient behind it
|
2026-09-09 08:38:55 -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
|
cc50f1d026
|
feat: contain the signature editing surface so a stored inline position:fixed covers its own box instead of a teammate's dashboard, since the visual editor mounts that markup with innerHTML and forcing the source view for every inline style would take the visual editor away from almost every real signature, and stop the editor's table rule overriding a template's own width attribute, which edited a 600px design at full container width and discarded its column widths
|
2026-09-09 08:33:46 -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
|
538dc29575
|
feat: stop the CSS inliner deleting the half of a selector list it cannot take over, so a rule like ".btn, .other:hover" keeps its declaration for the hover half after the first inlines, leave a rule whose selector list cannot be split on commas exactly as the author wrote it because a comma inside :is() or an attribute value belongs to the selector and rejoining the pieces writes a different one, keep a rule that matches nothing today rather than dropping it, and declare the paste-adoption ref above the editor that closes over it as the other paste-time refs already are
|
2026-09-09 08:20:23 -07:00 |
|
Matthew Meszaros
|
8157f3c2c0
|
feat: read the effective dashboard URL through the instancecheck package's own appURL helper in the insecure-URL check rather than reaching into config directly
|
2026-09-09 08:19:20 -07:00 |
|
Matthew Meszaros
|
c91363ca5e
|
feat: seed the mailhtml fuzz target with the two inputs that found real bugs, an unparsable at-rule and a body whose case fold changes its length, so the seeds guard both fixes on every go test rather than only when the fuzzer is run
|
2026-09-09 08:04:32 -07:00 |
|
Matthew Meszaros
|
4b6de5e807
|
feat: stop the send path corrupting a body whose case fold changes its length, found by fuzzing internal/pkg/mailhtml, since lastRealTag lowercased a copy and indexed the original so a Turkish capital I or a Kelvin sign shifted every offset and put the signature and opt-out footer at the wrong byte or past the end of the body, and stop InlineCSS deleting a stylesheet it could not parse by leaving the block byte for byte unless a rule actually moved onto an element, with a final guard returning the original whenever the pass would emit nothing from something
|
2026-09-09 07:25:28 -07:00 |
|
Matthew Meszaros
|
db2e6a657f
|
Merge pull request #395 from warmbly/refactor/automatic-worker-mailbox-placement
feat: scored worker placement and a pull-based fleet
|
2026-09-09 07:24:51 -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
|
50545a0c39
|
feat: name the join-script check's fs-blob environment for what it is instead of implying a list it cannot iterate, and force NODE_ENV empty for the default render so a value exported elsewhere in the repo cannot decide which unit the check asserts on
|
2026-09-09 07:18:32 -07:00 |
|
Matthew Meszaros
|
0fd64db51d
|
feat: define the join-script render variants once so a new one extends every per-render assertion instead of only the first, and move the checker's own parse check out of the shellcheck branch where it was skipped exactly when shellcheck was missing and ran under whatever shell happened to be sh
|
2026-09-09 07:15:08 -07:00 |
|
Matthew Meszaros
|
031c1dcd0a
|
feat: check every rendered unit variant for a node-writable EnvironmentFile rather than only the default one, because dropping it as redundant is the reasoning that already lost this guard once and the assertion exists for changes nobody has made yet, and parse-check the checker itself alongside shellchecking it
|
2026-09-09 07:09:07 -07:00 |
|
Matthew Meszaros
|
a5993a1ed0
|
feat: put the standalone-statement note where the docs said it was, shellcheck the join-script checker as well since its own disable directives are load-bearing, and stop claiming the EnvironmentFile assertion covers two render variants when only the mount list varies with the environment
|
2026-09-09 07:04:58 -07:00 |
|
Matthew Meszaros
|
9203695a41
|
feat: harden the join-script guard against being fooled rather than against being reformatted, matching the ensure_blob_root call on its first field after a looser regex proved satisfiable by the name appearing inside a warn string, capturing the function body without a pipeline so a renamed function reports that instead of the assertion it happened to fail, and rendering both unit variants so the no-blob one is covered too
|
2026-09-09 06:59:14 -07:00 |
|
Matthew Meszaros
|
f0bc0cf684
|
Merge pull request #397 from warmbly/fix/issue-396
Fix: inbox scroll position resets to top when opening emails
|
2026-09-09 06:57:55 -07:00 |
|
Matthew Meszaros
|
9f804c8662
|
feat: restore the assertion that no EnvironmentFile points into the node-writable mount, which the previous commit deleted as redundant when it is not since the surviving check only proves the correct line exists and not that a second one is absent, and match the ensure_blob_root call in command position after stripping comments so a one-liner reformat no longer fails the build
|
2026-09-09 06:54:17 -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
|
eb4e1a8bb8
|
feat: make the join-script checker survive its own mutation tests, matching the ensure_blob_root call on the command field so a commented-out call no longer passes, tolerating a space before the parentheses in a function definition, and capturing the function body before asserting so a renamed function reports that rather than the assertion it happened to fail
|
2026-09-09 06:47:56 -07:00 |
|
Matthew Meszaros
|
6183e8b238
|
feat: close the join-script checker's blind spots by pinning the image reference outside the node-writable mount, asserting at their call sites the two invariants that leave no trace in the rendered unit, and matching call lines rather than any line mentioning the word so a comment containing enrolment cannot satisfy the ordering check
|
2026-09-09 06:42:56 -07:00 |
|
Matthew Meszaros
|
7f2bfa252e
|
feat: hold the campaign preview request until writing settles, since HTML mode runs it from the Edit tab and a designed body is tens of kilobytes to ship on every 250ms pause, and treat a document sitting behind a leading comment as one when deciding whether to adopt a paste as HTML or to open an A/B arm as markup, because a saved email opens with the tool that wrote it before its doctype
|
2026-09-09 06:39:23 -07:00 |
|
Matthew Meszaros
|
6947aca9b3
|
feat: make the join-script check assert on what the script renders via a new --print-unit mode, after the previous version compared a heredoc copied into the checker and stayed green when the systemd command-substitution bug was put back, and make docker_mounts pure so the unit can be rendered without creating directories
|
2026-09-09 06:37:56 -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
|
29bf9b4320
|
feat: cover the join script with make join-check, wired into make lint, since nothing tested the highest-consequence non-Go file in the repo and three defects reached the branch through it; and fix the four the review found in the last round, matching the fs provider alias, creating blob parents under a 0022 umask rather than the 0700 one write_config leaves set, warning instead of silently mounting a blob root the node cannot write, and validating the path right after enrolment rather than halfway through the install
|
2026-09-09 06:28:30 -07:00 |
|
Matthew Meszaros
|
03255754ff
|
feat: read the blob settings from the env in memory so the dry run reports what a real join would do instead of a previous join's file, create the blob root 0755 rather than inheriting the 0700 umask a co-located backend cannot read, refuse a relative BLOB_FS_ROOT that docker cannot mount, and stop discarding the errors that made a failed mount look like a successful join
|
2026-09-09 06:12:56 -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
|
4fc6d1db77
|
feat: put every docker mount on one line in the generated unit, because command substitution strips the trailing newline and my previous multi-line blob mount collapsed the continuation so the node restart-looped on any filesystem-blob install, and mount BLOB_FS_ROOT only when it already exists rather than creating and recursively chowning a directory a co-located bare-metal backend owns
|
2026-09-09 05:59:44 -07:00 |
|
Matthew Meszaros
|
0a08983158
|
feat: keep the node's write path reachable and the image reference root-owned, by granting traverse on the state directory without making it writable, re-owning image-ref on every join so a machine joined by the earlier chown-everything script converges, mounting BLOB_FS_ROOT separately when blobs live under the state directory the mount no longer covers, and renaming the reserve dialog's remaining convert-to-dedicated button
|
2026-09-09 05:53:28 -07:00 |
|
Matthew Meszaros
|
92470e1528
|
feat: drop the racy liveness re-read from the placement live test, which re-queried whichever worker placement chose and so raced with the email package's live tests creating and deleting their own worker in a parallel package; that placement only ever picks a live worker is already covered by the unit tests, which need no database
|
2026-09-09 05:44:25 -07:00 |
|
Matthew Meszaros
|
65fe82c472
|
feat: stop the node container being able to choose the image root runs, by giving it a dedicated uid-1000 subdirectory to write its update target into instead of making the whole state directory writable, validating the tag it asks for before the updater acts on it, and no longer recursively chowning a directory a bare-metal install also keeps its blobs in
|
2026-09-09 05:41:16 -07:00 |
|
Matthew Meszaros
|
bfdbd77b04
|
feat: fix the eight defects the second review pass found, including three where the previous fix did not land: the bind-mounted state dir was root-owned so the node running as uid 1000 still could not write its update target, elevating the reserved-worker eviction did nothing because the rotation loop bailed on target-equals-current before the urgency was consulted, and the warmup-pool assertion was vacuous which hid that warmupPoolFor checked the subscription repo before the billing provider and answered free on a self-host install
|
2026-09-09 05:32:30 -07:00 |
|