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
2998f8a8c6
Merge remote-tracking branch 'origin/main' into feat/worker-capacity-soft-target
2026-09-10 06:26:47 -07:00
Matthew Meszaros
fa2b5330d7
feat: drop the auth-pressure placement term because worker_capacity_view aggregates auth_errors (per-mailbox credential failures) and not rate_limit_errors (the 454/421 per-IP throttles it claimed to measure), measure projected utilization against an age-free Capacity.Target so a freshly joined node can relieve a full fleet instead of scoring as 200% loaded after one mailbox, bound the isolated-egress override with an explicit OverTarget check now that Eligible no longer caps it, and cap rotation moves per destination since a tick scores every mailbox against one frozen materialized-view snapshot
2026-09-10 05:51:17 -07:00
Matthew Meszaros
7fa4fdfbc4
feat: make worker capacity a placement target rather than a hard gate, so Eligible refuses only on health and an over-target worker costs enough score to lose to anything with room instead of returning nil and dropping assignment into selectFallback, score projected utilization including the incoming mailbox's own weight, and penalise the 454/421 auth pressure the capacity view already collected and threw away
2026-09-10 05:38:17 -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
1e1231f3b1
feat: address the review of the API key delete, putting the permanent-delete route in the published OpenAPI contract with its 409, keeping one auto margin in the drawer footer so the Delete button lands on the right, and deciding the dashboard's status pill, its footer and the key-count strip on whether the key can still authenticate rather than on a status column that never says expired
2026-09-10 05:23:56 -07:00
Matthew Meszaros
64dfaa8c60
Merge remote-tracking branch 'origin/main' into feat/split-cloud-hosting
2026-09-10 14:20:22 +02:00
Matthew Meszaros
510ee692ba
feat: address the review on the split-deployment branch by moving the two broker routes onto their own NODE_BROKER_TOKEN so the internet-facing tracking and forms services no longer hold a credential that can open any organization's data key, refusing to presign any key outside the prefixes a node reaches, fixing IAM policies that named an alias ARN KMS never resolves in a Resource element, bounding both brokered HTTP clients because the sync loop's context never expires, no longer reporting a 403 from the object store as a missing body, and redacting the DSN and URL credentials the dry-run listing printed in clear
2026-09-10 14:19:53 +02: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
9d6f71d683
feat: match every word of a contact search against first name, last name, email, company and phone instead of taking the query whole, so "Test Demo" finds the contact whose name is split across two columns (issue #413 ), cap a search at six words, and say in the contacts guide what search matches
2026-09-10 04:53:50 -07:00
Matthew Meszaros
08f1420c0b
Merge branch 'main' into fix/issue-410-scanner-networks
2026-09-10 04:32:36 -07:00
Matthew Meszaros
6684dafe3a
feat: classify opens and clicks that arrive from a known mail-filtering network as automated, so Microsoft 365 Defender's delivery-time pixel fetches and Safe Links URL detonations stop counting as engagement (issue #410 ), with a shipped scanner catalogue, TRACKING_SCANNER_* overrides and optional ASN matching from a trusted edge header
2026-09-10 03:38:50 -07:00
Matthew Meszaros
6dcbc6d6a7
feat: stop sending prompt=consent on the Outlook/Microsoft 365 authorize request, which made Entra ID re-run the consent eligibility check per sign-in and refuse every non-admin with AADSTS90095 even under a tenant-wide admin grant (issue #409 ), and ask for prompt=select_account instead so the account picker survives while Google keeps access_type=offline plus forced consent for its refresh token
2026-09-10 03:24:16 -07:00
Matthew Meszaros
6fe92c985f
feat: stop asking a server without CONDSTORE for MODSEQ, which made every FETCH a malformed fetch-att that IONOS answered BAD so the mailbox synced nothing on every pass (issue #405 ), and gate the LIST RETURN options on LIST-EXTENDED for the same reason
2026-09-09 19:45:01 -07:00
Matthew Meszaros
054115ba91
Merge branch 'main' into fix/issue-405-imap-error-noise
2026-09-09 10:13:28 -07:00
Matthew Meszaros
2d20e1a6a0
Merge branch 'main' into fix/unibox-threading-and-transport-errors
2026-09-09 10:08:57 -07:00
Matthew Meszaros
bb2b2b1743
feat: make the one-unresolved-error-per-code rule a partial unique index with a conflict path, collapsing the duplicate rows a repeating mail error already left behind
2026-09-09 09:46:39 -07:00
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
568bdb48ba
feat: never render an empty IMAP error detail and close the CRM rail when the viewport narrows past lg
2026-09-09 09:24:43 -07:00
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
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
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
SUMAN JANA
73d9c18bfe
fix(imap, unibox): show the server's text for a codeless IMAP error; contact rail starts closed
...
- An IMAP NO/BAD without a response code (Gmail's "NO System Error")
rendered as "Something went wrong: " in the mailbox's error list. Fall
back to the server's text when the code is empty.
- The unibox contact rail opened by itself on lg+ screens, putting the
contact form in front of every thread the reader opened. It now starts
closed at every width and opens from the header toggle.
2026-09-09 15:47:50 +00: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
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
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
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
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