Skip to content

Fix: Campaign subject is now interpolated with contact variables#397

Open
BenoitPrmt wants to merge 2 commits intousesend:mainfrom
BenoitPrmt:fix/campaign-subject-doesnt-have-variables
Open

Fix: Campaign subject is now interpolated with contact variables#397
BenoitPrmt wants to merge 2 commits intousesend:mainfrom
BenoitPrmt:fix/campaign-subject-doesnt-have-variables

Conversation

@BenoitPrmt
Copy link
Copy Markdown

@BenoitPrmt BenoitPrmt commented May 7, 2026

Closes #396

Campaign subject is now interpolated with contact variables


Summary by cubic

Fixes campaign email subjects so contact variables interpolate per recipient with fallbacks, preventing raw placeholders from being sent. Addresses #396.

  • Bug Fixes

    • Interpolate subjects per contact in campaign-service and again at send time in email-queue-service; persist the updated subject when it changes.
    • Support built-ins (email, firstName, lastName), contact book variables, contact. prefix, and fallback= with optional whitespace; leave unknown variables unchanged.
  • Refactors

    • Centralized variable replacement in server/utils/contact-variable-replacement; removed duplicate logic and expanded unit tests.

Written for commit 0e55612. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Email subjects now support dynamic contact variable substitution for personalization (built-in fields and custom contact properties), with fallback values.
    • Per-contact subject substitutions are applied and stored so sent emails show the resolved subject.
  • Tests

    • Added unit tests covering built-in and custom variable replacement, fallback behavior, and unknown-variable handling.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 7, 2026

@BenoitPrmt is attempting to deploy a commit to the kmkoushik's projects Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a589dba7-31ef-4cf4-be9f-63b1a73ad1de

📥 Commits

Reviewing files that changed from the base of the PR and between 8624b67 and 0e55612.

📒 Files selected for processing (1)
  • apps/web/src/server/utils/contact-variable-replacement.unit.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/src/server/utils/contact-variable-replacement.unit.test.ts

Walkthrough

This PR extracts contact variable replacement into a shared utility and applies per-contact interpolation to email subjects. campaign-service removes the inline replacement logic and uses replaceContactVariables when creating suppressed and non-suppressed emails. EmailQueueService now conditionally resolves and persists an interpolated subject at send-time for campaign-linked emails. A new test suite validates built-in and custom variables, fallback parsing, dotted lookups, nullable built-ins, and unknown-variable behavior.

Possibly related PRs

  • usesend/useSend#359: Introduces the contact-book variable registry used by the allowedVariables parameter in this PR's replacement logic.
🚥 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 'Fix: Campaign subject is now interpolated with contact variables' directly and clearly describes the main change in the PR—applying variable interpolation to campaign email subjects.
Linked Issues check ✅ Passed The PR fully addresses issue #396 by refactoring variable replacement into shared utilities, applying replaceContactVariables to both campaign-service and email-queue-service subject handling, and adding comprehensive unit tests covering built-in variables, custom variables, fallback syntax, and edge cases.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing campaign subject interpolation: refactoring variable replacement utilities, updating campaign-service and email-queue-service to apply interpolation to subjects, and adding test coverage for the fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
apps/web/src/server/utils/contact-variable-replacement.unit.test.ts (2)

6-6: 💤 Low value

Import should use the ~/ alias per coding guidelines.

-} from "./contact-variable-replacement";
+} from "~/server/utils/contact-variable-replacement";

As per coding guidelines: "Use the ~/ alias for imports from src in apps/web."

🤖 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/server/utils/contact-variable-replacement.unit.test.ts` at line
6, Update the import in contact-variable-replacement.unit.test.ts to use the
project src alias instead of a relative path: replace the current import from
"./contact-variable-replacement" with the aliased module path (e.g.
"~/server/utils/contact-variable-replacement") so the test imports the same
module via the ~/ alias per coding guidelines.

23-59: ⚡ Quick win

Add tests for null built-in fields with fallback and contact. prefix notation.

Two meaningful edge cases are missing:

  1. contact.firstName (or lastName) is null — the fallback should be used. This is important because firstName/lastName are nullable in Prisma, and the branch at line 113 (if (contactValue && contactValue.length > 0)) relies on the falsy-null guard to emit the fallback.
  2. {{contact.firstName}} notation — the regex supports (?:contact\.)? prefix but this path is untested.
🧪 Suggested additional tests
+  it("uses fallback when built-in field is null", () => {
+    const contactWithNullName = { ...baseContact, firstName: null };
+    expect(
+      replaceContactVariables(
+        "Hello {{firstName,fallback=Friend}}",
+        contactWithNullName as Contact,
+        [...BUILT_IN_CONTACT_VARIABLES],
+      ),
+    ).toBe("Hello Friend");
+  });
+
+  it("replaces variables with contact. prefix notation", () => {
+    expect(
+      replaceContactVariables("Hello {{contact.firstName}}", baseContact, [
+        ...BUILT_IN_CONTACT_VARIABLES,
+      ]),
+    ).toBe("Hello Benoît");
+  });
🤖 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/server/utils/contact-variable-replacement.unit.test.ts` around
lines 23 - 59, Add unit tests covering nullable built-in fields and the optional
contact. prefix: create a contact fixture with firstName (and/or lastName) set
to null and assert replaceContactVariables returns the provided fallback when
using "{{firstName,fallback=you}}" and also assert the same behavior when using
the prefixed form "{{contact.firstName,fallback=you}}"; reference the
replaceContactVariables function and BUILT_IN_CONTACT_VARIABLES/ baseContact
fixture to locate where to add tests.
🤖 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/server/utils/contact-variable-replacement.unit.test.ts`:
- Line 6: Update the import in contact-variable-replacement.unit.test.ts to use
the project src alias instead of a relative path: replace the current import
from "./contact-variable-replacement" with the aliased module path (e.g.
"~/server/utils/contact-variable-replacement") so the test imports the same
module via the ~/ alias per coding guidelines.
- Around line 23-59: Add unit tests covering nullable built-in fields and the
optional contact. prefix: create a contact fixture with firstName (and/or
lastName) set to null and assert replaceContactVariables returns the provided
fallback when using "{{firstName,fallback=you}}" and also assert the same
behavior when using the prefixed form "{{contact.firstName,fallback=you}}";
reference the replaceContactVariables function and BUILT_IN_CONTACT_VARIABLES/
baseContact fixture to locate where to add tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3d3c1841-43c3-44ee-b5bb-93c33d19b626

📥 Commits

Reviewing files that changed from the base of the PR and between 964bbf9 and 8624b67.

📒 Files selected for processing (4)
  • apps/web/src/server/service/campaign-service.ts
  • apps/web/src/server/service/email-queue-service.ts
  • apps/web/src/server/utils/contact-variable-replacement.ts
  • apps/web/src/server/utils/contact-variable-replacement.unit.test.ts

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

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.

🐞 - Campaign subject is not interpolated with contact variables (only HTML body is)

1 participant