v35.0

rowboatlabs/rowboatv35.0Jul 8, 2026by pierre-b

AI Summary

This release focuses on performance improvements for email rendering, introduces per-link click analytics for broadcasts, and fixes critical bugs regarding contact selection, authentication emails, and A/B testing attribution.

Key Highlights

  • Broadcast reports now display per-link click breakdowns (total and unique) while preserving UTM parameters.
  • Email rendering is significantly faster via Liquid engine pooling and regex compilation, cutting processing time by up to 8x.
  • AI Email Designer reliability is improved with streaming reasoning, better error handling, and OpenAI-compatible integrations.
  • Security update resolves a cross-site scripting (XSS) advisory in the echarts dependency.
  • Contacts multi-select synchronization is fixed to prevent phantom selections and stale data issues.

Breaking Changes

  • Workspace migration v35 adds a new `message_history.clicked_links` JSONB column to the database.
  • One-time authentication emails (Supabase) no longer rewrite links through click-tracking, requiring a tri-state `tracking_mode` setting.
  • Automation 'Add to List' node fix changes the stored `subscribed` status from invalid to valid values (`active`/`pending`).

New Features

  • Search templates by name with debounced filtering and URL persistence.
  • One-time auth emails (Supabase) gain a `tracking_mode` to disable link rewriting.
  • Concurrent template edits are now conflict-resolved with a 409 Conflict status.
  • Faster email rendering via Liquid engine reuse across blocks.
  • AI Email Designer reliability improvements include streaming reasoning and MJML error visibility.

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).