Skip to content

fix: expose campaign unsubscribe variable#433

Merged
KMKoushik merged 3 commits into
mainfrom
codex/document-campaign-unsubscribe-variable
Jul 21, 2026
Merged

fix: expose campaign unsubscribe variable#433
KMKoushik merged 3 commits into
mainfrom
codex/document-campaign-unsubscribe-variable

Conversation

@KMKoushik

@KMKoushik KMKoushik commented Jul 20, 2026

Copy link
Copy Markdown
Member

Summary

  • document {{usesend_unsubscribe_url}} as the required, recipient-specific campaign unsubscribe variable
  • add the canonical variable to campaign editor autocomplete while preserving contact-book variable deduplication
  • resolve structured unsubscribe variable nodes to each recipient's URL
  • update the campaign API example so it includes the required unsubscribe link

Impact

Campaign authors can discover the required unsubscribe variable in both the guide and editor, and autocomplete selections render to the correct per-recipient URL.

Screenshots

Not included: the existing autocomplete menu only gains one text suggestion; there are no layout or styling changes.

Migrations

None.

Verification

  • pnpm exec vitest run -c vitest.unit.config.ts src/lib/constants/campaign.unit.test.ts src/server/service/campaign-service.unit.test.ts (8 tests passed)
  • pnpm --filter=web typecheck
  • ESLint on the changed campaign constants, tests, and service with zero warnings
  • Prettier check on all changed files

Closes #427

Summary by CodeRabbit

  • New Features
    • Added unsubscribe token support to campaign personalization using {{usesend_unsubscribe_url}}.
    • Campaign editor variables now automatically include the unsubscribe token alongside standard and contact-book variables.
  • Bug Fixes
    • Improved email rendering so both the canonical and legacy unsubscribe placeholders are replaced with recipient-specific URLs (no unresolved tokens left behind).
  • Documentation
    • Updated the personalization guide and the “create a campaign that uses variables” example with an unsubscribe HTML snippet.
  • Tests
    • Added unit tests covering variable merging and unsubscribe URL replacement behavior.

@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
unsend-marketing Ready Ready Preview, Comment Jul 20, 2026 11:24pm

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Campaign personalization now documents {{usesend_unsubscribe_url}} and includes it in the API example. Shared helpers add the canonical unsubscribe variable to editor variables and map both current and legacy unsubscribe names to each recipient’s unsubscribe URL during rendering. Tests cover variable inclusion, deduplication, and HTML replacement.

Possibly related PRs

  • usesend/useSend#359: Modifies campaign editor variable construction and rendering variable substitution.
  • usesend/useSend#374: Introduces the campaign personalization guide updated by this change.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: exposing the campaign unsubscribe variable.
Linked Issues check ✅ Passed The PR documents the required variable, adds it to editor autocomplete, includes an example, and preserves legacy spelling support.
Out of Scope Changes check ✅ Passed The code changes stay within the issue scope and supporting test/documentation updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

apps/web/src/lib/constants/campaign.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

apps/web/src/lib/constants/campaign.unit.test.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 20, 2026

Copy link
Copy Markdown

Deploying usesend with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8eadf67
Status: ✅  Deploy successful!
Preview URL: https://27508f48.usesend.pages.dev
Branch Preview URL: https://codex-document-campaign-unsu.usesend.pages.dev

View logs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/lib/constants/campaign.ts`:
- Around line 18-20: The helper getCampaignUnsubscribeVariableValues must return
both canonical and legacy unsubscribe variable keys, each mapped to
unsubscribeUrl, while keeping only the canonical key in editor autocomplete. In
apps/web/src/lib/constants/campaign.ts lines 18-20, update the helper
accordingly; in apps/web/src/server/service/campaign-service.ts lines 119-143,
continue spreading its result into variableValues and add a regression test
covering the legacy variable in non-link structured content.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3d8e3e71-58f3-4762-972e-e1ea899db58c

📥 Commits

Reviewing files that changed from the base of the PR and between e354f85 and ac60409.

📒 Files selected for processing (3)
  • apps/docs/guides/campaign-personalization.mdx
  • apps/web/src/lib/constants/campaign.ts
  • apps/web/src/server/service/campaign-service.ts
💤 Files with no reviewable changes (1)
  • apps/docs/guides/campaign-personalization.mdx

Comment on lines +18 to +20
export function getCampaignUnsubscribeVariableValues(unsubscribeUrl: string) {
return { [CAMPAIGN_UNSUBSCRIBE_VARIABLE]: unsubscribeUrl };
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Complete legacy unsubscribe compatibility in structured rendering.

The shared variable-value contract exposes only usesend_unsubscribe_url, while the service claims to preserve unsend_unsubscribe_url. Return both aliases from the helper; keep only the canonical name in editor autocomplete.

  • apps/web/src/lib/constants/campaign.ts#L18-L20: return both unsubscribe variable keys mapped to unsubscribeUrl.
  • apps/web/src/server/service/campaign-service.ts#L119-L143: continue spreading the helper result into variableValues and add a regression test for legacy variables in non-link structured content.
📍 Affects 2 files
  • apps/web/src/lib/constants/campaign.ts#L18-L20 (this comment)
  • apps/web/src/server/service/campaign-service.ts#L119-L143
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/lib/constants/campaign.ts` around lines 18 - 20, The helper
getCampaignUnsubscribeVariableValues must return both canonical and legacy
unsubscribe variable keys, each mapped to unsubscribeUrl, while keeping only the
canonical key in editor autocomplete. In apps/web/src/lib/constants/campaign.ts
lines 18-20, update the helper accordingly; in
apps/web/src/server/service/campaign-service.ts lines 119-143, continue
spreading its result into variableValues and add a regression test covering the
legacy variable in non-link structured content.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/web/src/lib/constants/campaign.unit.test.ts (1)

10-15: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Export and reuse the legacy variable constant.

Instead of hardcoding "unsend_unsubscribe_url", consider exporting LEGACY_CAMPAIGN_UNSUBSCRIBE_VARIABLE from campaign.ts and using it here (and on line 60). This removes magic strings and ensures the test stays aligned with the implementation.

🛠️ Proposed refactor

In apps/web/src/lib/constants/campaign.ts:

-const LEGACY_CAMPAIGN_UNSUBSCRIBE_VARIABLE = "unsend_unsubscribe_url";
+export const LEGACY_CAMPAIGN_UNSUBSCRIBE_VARIABLE = "unsend_unsubscribe_url";

In this file:

-import {
-  CAMPAIGN_UNSUBSCRIBE_VARIABLE,
-  getCampaignEditorVariables,
-  getCampaignUnsubscribeVariableValues,
-} from "~/lib/constants/campaign";
+import {
+  CAMPAIGN_UNSUBSCRIBE_VARIABLE,
+  LEGACY_CAMPAIGN_UNSUBSCRIBE_VARIABLE,
+  getCampaignEditorVariables,
+  getCampaignUnsubscribeVariableValues,
+} from "~/lib/constants/campaign";

...

   it("includes the canonical unsubscribe variable", () => {
     const variables = getCampaignEditorVariables();
 
     expect(variables).toContain(CAMPAIGN_UNSUBSCRIBE_VARIABLE);
-    expect(variables).not.toContain("unsend_unsubscribe_url");
+    expect(variables).not.toContain(LEGACY_CAMPAIGN_UNSUBSCRIBE_VARIABLE);
   });

...

   it("renders a legacy structured unsubscribe variable with the recipient URL", async () => {
-    const legacyVariable = "unsend_unsubscribe_url";
+    const legacyVariable = LEGACY_CAMPAIGN_UNSUBSCRIBE_VARIABLE;
     const renderer = new EmailRenderer({
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/lib/constants/campaign.unit.test.ts` around lines 10 - 15,
Export LEGACY_CAMPAIGN_UNSUBSCRIBE_VARIABLE from campaign.ts, then import and
reuse it in campaign.unit.test.ts for both legacy-variable assertions, replacing
the hardcoded "unsend_unsubscribe_url" strings while preserving the existing
expectations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/web/src/lib/constants/campaign.unit.test.ts`:
- Around line 10-15: Export LEGACY_CAMPAIGN_UNSUBSCRIBE_VARIABLE from
campaign.ts, then import and reuse it in campaign.unit.test.ts for both
legacy-variable assertions, replacing the hardcoded "unsend_unsubscribe_url"
strings while preserving the existing expectations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 04b604a7-a0b3-4627-a3fd-5e50a0094319

📥 Commits

Reviewing files that changed from the base of the PR and between ac60409 and 8eadf67.

📒 Files selected for processing (2)
  • apps/web/src/lib/constants/campaign.ts
  • apps/web/src/lib/constants/campaign.unit.test.ts

@KMKoushik
KMKoushik merged commit b1b1f86 into main Jul 21, 2026
6 checks passed
@KMKoushik
KMKoushik deleted the codex/document-campaign-unsubscribe-variable branch July 21, 2026 04:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Docs: {{usesend_unsubscribe_url}} variable is not documented

1 participant