v35.0

danielmiessler/Fabricv35.0Jul 8, 2026by pierre-b

AI Summary

This release introduces comprehensive improvements to broadcast analytics, email rendering performance, and AI Email Designer reliability. It also addresses critical stability issues in contact management, automation workflows, and database connections, alongside a security patch.

Key Highlights

  • Broadcast reports now provide per-link click breakdown (total and unique clickers) with UTM parameter tracking.
  • Faster email rendering achieved by reusing Liquid engines and compiling regexes once.
  • AI Email Designer reliability improvements including DeepSeek support, error handling, and a reasoning-effort selector.
  • Security update: Bumped echarts dependency to 6.1.0 to resolve an XSS advisory.
  • Fixed contacts multi-select synchronization and resolved A/B-test broadcast attribution issues.

Breaking Changes

  • Database migration v35 adds a new `message_history.clicked_links` JSONB column.
  • One-time authentication emails (Supabase) are no longer rewritten via click-tracking; `tracking_mode` field is introduced.

New Features

  • Search templates by name on the templates list with client-side filtering.
  • Broadcast reports showing per-link click breakdown.
  • AI Email Designer reasoning-effort selector for OpenAI-compatible integrations.

Full Release Notes

- **Feature**: Broadcast reports now show a per-link click breakdown — total clicks and unique clickers per destination URL (UTM parameters kept, since they are commonly used to tell links apart). Clicks are recorded per URL in a new `message_history.clicked_links` JSONB column added by workspace migration v35; recording starts immediately after the upgrade, including for emails already sent (the destination URL is embedded in their tracking tokens), so the breakdown only covers post-upgrade clicks and legacy `/visit` links remain aggregate-only (#339, #311).
- **Feature**: Search templates by name on the templates list — a debounced name search beside the category filter, persisted in the URL, mirroring the broadcasts search (the already-loaded list is filtered client-side).
<img width="1160" height="743" alt="Capture d’écran 2026-07-08 à 11 40 58" src="https://github.com/user-attachments/assets/402c9329-cfba-41b1-bf15-2e7d9f42d171" />

- **Improvement**: Faster email rendering — Liquid engines are now reused across blocks via a pool instead of being rebuilt for every block, and constant regexes are compiled once, cutting per-block Liquid processing time by ~3× (~8× under concurrent sends) during broadcasts (#381).
- **Improvement**: AI Email Designer reliability with reasoning models (e.g. DeepSeek) — thinking now streams into a collapsible panel, the preview refreshes immediately after AI edits, a generated email that doesn't compile surfaces the MJML error instead of silently looking successful, hitting the token limit shows a non-destructive warning instead of wiping the answer, and OpenAI-compatible integrations gain a reasoning-effort selector (#363).
- **Fix**: The contacts multi-select no longer gets out of sync when deleting via the row's ⋯ menu — clicking a dropdown item invisibly toggled the row's selection (the click bubbles through the React portal to the row's select-on-click handler), and a deleted contact was never removed from the selection, leaving a phantom "N contacts selected" bar whose stale emails a later bulk *Add to list* would even re-create via upsert (#374).
- **Fix**: One-time authentication emails (the built-in Supabase notifications: magic link, signup confirmation, invite, recovery, email change) are no longer rewritten through the click-tracking redirect — their links previously depended on the API being up and could be consumed by mailbox link scanners before the user clicked. Transactional notifications gain a tri-state `tracking_mode` (inherit the workspace setting, or disabled — which also suppresses UTM rewriting); an absent field on update keeps the stored value so partial edits cannot wipe an opt-out, and migration v35 sets `disabled` on existing Supabase notifications.
- **Fix**: A/B-test broadcasts no longer attribute most recipients' link clicks and `{{ utm_content }}` values to the wrong variant. When no explicit `utm_content` was set, the first processed recipient's template ID was written back onto the shared broadcast object and frozen for every later recipient — and in batch sends the template data even lagged one recipient behind the rewritten links. `utm_content` is now computed per recipient from the variant actually chosen for them, and `utm_term` is applied consistently across both send paths.
- **Fix**: The automation *Add to List* node no longer fails silently. Its "Subscribed" option stored an invalid `subscribed` status that the backend rejected (only `active`/`pending` are valid), so contact journeys stalled at that node with no visible error. The editor now stores `active`, node configurations are validated when the automation is saved so an invalid status is caught immediately, and a data migration repairs existing automations, contact lists, and timeline entries that still carry `subscribed` (#376).
- **Improvement**: Concurrent template edits no longer silently overwrite each other. Template saves were last-writer-wins, so a save based on a stale revision clobbered newer changes. The editor now sends the revision it was based on and the server rejects a stale-base save with `409 Conflict`, prompting to reload the latest or overwrite. Covers the email template, blog post, and transactional-notification editors (#378).
- **Security**: Bumped the console's `echarts` dependency to 6.1.0 to resolve a cross-site scripting (XSS) advisory (GHSA-fgmj-fm8m-jvvx) flagged by Dependabot.
- **Fix**: Resolved intermittent `pq: password authentication failed` / "failed to get workspace connection" errors under load. The workspace connection manager health-check-pinged the cached pool on every query and evicted, closed, and rebuilt it (re-hitting the `postgres` admin database) on any slow ping or transient blip; it now reuses cached pools and creates them without a global lock (#380).
- **Fix**: Adding a contact (or adding a contact to a list from the details drawer) now refreshes the contacts list immediately instead of requiring a hard page reload. The "Add" contact drawer never invalidated the React Query cache on success, and the "add to list" action only refreshed the contact details — both now invalidate the contacts list (and total count) so the new contact appears right away (#364).