Skip to content

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

Merged
KMKoushik merged 2 commits into
usesend:mainfrom
BenoitPrmt:fix/campaign-subject-doesnt-have-variables
May 16, 2026
Merged

Fix: Campaign subject is now interpolated with contact variables#397
KMKoushik merged 2 commits into
usesend:mainfrom
BenoitPrmt:fix/campaign-subject-doesnt-have-variables

Conversation

@BenoitPrmt

@BenoitPrmtBenoitPrmt commented May 7, 2026

Copy link
Copy Markdown
Contributor

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

vercelBot commented May 7, 2026

Copy link
Copy Markdown

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

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown

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

coderabbitaiBot commented May 7, 2026

Copy link
Copy Markdown
Contributor

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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe 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✅ PassedThe 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✅ PassedAll 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✅ PassedNo 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.

@coderabbitaicoderabbitaiBot 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 (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

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown

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.

@KMKoushik
KMKoushik merged commit 31a49fb into usesend:mainMay 16, 2026
1 check passed
Sign up for freeto 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)

2 participants

@BenoitPrmt@KMKoushik
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Fix: Campaign subject is now interpolated with contact variables by BenoitPrmt · Pull Request #397 · usesend/useSend · GitHub
Skip to content

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

Merged
KMKoushik merged 2 commits into
usesend:mainfrom
BenoitPrmt:fix/campaign-subject-doesnt-have-variables
May 16, 2026
Merged

Fix: Campaign subject is now interpolated with contact variables#397
KMKoushik merged 2 commits into
usesend:mainfrom
BenoitPrmt:fix/campaign-subject-doesnt-have-variables

Conversation

@BenoitPrmt

@BenoitPrmtBenoitPrmt commented May 7, 2026

Copy link
Copy Markdown
Contributor

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

vercelBot commented May 7, 2026

Copy link
Copy Markdown

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

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown

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

coderabbitaiBot commented May 7, 2026

Copy link
Copy Markdown
Contributor

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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe 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✅ PassedThe 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✅ PassedAll 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✅ PassedNo 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.

@coderabbitaicoderabbitaiBot 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 (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

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown

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.

@KMKoushik
KMKoushik merged commit 31a49fb into usesend:mainMay 16, 2026
1 check passed
Sign up for freeto 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)

2 participants

@BenoitPrmt@KMKoushik
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix: Campaign subject is now interpolated with contact variables by BenoitPrmt · Pull Request #397 · usesend/useSend · GitHub
Skip to content

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

Merged
KMKoushik merged 2 commits into
usesend:mainfrom
BenoitPrmt:fix/campaign-subject-doesnt-have-variables
May 16, 2026
Merged

Fix: Campaign subject is now interpolated with contact variables#397
KMKoushik merged 2 commits into
usesend:mainfrom
BenoitPrmt:fix/campaign-subject-doesnt-have-variables

Conversation

@BenoitPrmt

@BenoitPrmtBenoitPrmt commented May 7, 2026

Copy link
Copy Markdown
Contributor

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

vercelBot commented May 7, 2026

Copy link
Copy Markdown

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

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown

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

coderabbitaiBot commented May 7, 2026

Copy link
Copy Markdown
Contributor

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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe 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✅ PassedThe 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✅ PassedAll 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✅ PassedNo 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.

@coderabbitaicoderabbitaiBot 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 (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

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown

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.

@KMKoushik
KMKoushik merged commit 31a49fb into usesend:mainMay 16, 2026
1 check passed
Sign up for freeto 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)

2 participants

@BenoitPrmt@KMKoushik
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix: Campaign subject is now interpolated with contact variables by BenoitPrmt · Pull Request #397 · usesend/useSend · GitHub
Skip to content

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

Merged
KMKoushik merged 2 commits into
usesend:mainfrom
BenoitPrmt:fix/campaign-subject-doesnt-have-variables
May 16, 2026
Merged

Fix: Campaign subject is now interpolated with contact variables#397
KMKoushik merged 2 commits into
usesend:mainfrom
BenoitPrmt:fix/campaign-subject-doesnt-have-variables

Conversation

@BenoitPrmt

@BenoitPrmtBenoitPrmt commented May 7, 2026

Copy link
Copy Markdown
Contributor

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

vercelBot commented May 7, 2026

Copy link
Copy Markdown

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

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown

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

coderabbitaiBot commented May 7, 2026

Copy link
Copy Markdown
Contributor

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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe 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✅ PassedThe 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✅ PassedAll 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✅ PassedNo 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.

@coderabbitaicoderabbitaiBot 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 (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

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown

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.

@KMKoushik
KMKoushik merged commit 31a49fb into usesend:mainMay 16, 2026
1 check passed
Sign up for freeto 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)

2 participants

@BenoitPrmt@KMKoushik
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Fix: Campaign subject is now interpolated with contact variables by BenoitPrmt · Pull Request #397 · usesend/useSend · GitHub
Skip to content

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

Merged
KMKoushik merged 2 commits into
usesend:mainfrom
BenoitPrmt:fix/campaign-subject-doesnt-have-variables
May 16, 2026
Merged

Fix: Campaign subject is now interpolated with contact variables#397
KMKoushik merged 2 commits into
usesend:mainfrom
BenoitPrmt:fix/campaign-subject-doesnt-have-variables

Conversation

@BenoitPrmt

@BenoitPrmtBenoitPrmt commented May 7, 2026

Copy link
Copy Markdown
Contributor

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

vercelBot commented May 7, 2026

Copy link
Copy Markdown

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

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown

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

coderabbitaiBot commented May 7, 2026

Copy link
Copy Markdown
Contributor

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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe 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✅ PassedThe 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✅ PassedAll 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✅ PassedNo 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.

@coderabbitaicoderabbitaiBot 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 (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

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown

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.

@KMKoushik
KMKoushik merged commit 31a49fb into usesend:mainMay 16, 2026
1 check passed
Sign up for freeto 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)

2 participants

@BenoitPrmt@KMKoushik
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix: Campaign subject is now interpolated with contact variables by BenoitPrmt · Pull Request #397 · usesend/useSend · GitHub
Skip to content

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

Merged
KMKoushik merged 2 commits into
usesend:mainfrom
BenoitPrmt:fix/campaign-subject-doesnt-have-variables
May 16, 2026
Merged

Fix: Campaign subject is now interpolated with contact variables#397
KMKoushik merged 2 commits into
usesend:mainfrom
BenoitPrmt:fix/campaign-subject-doesnt-have-variables

Conversation

@BenoitPrmt

@BenoitPrmtBenoitPrmt commented May 7, 2026

Copy link
Copy Markdown
Contributor

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

vercelBot commented May 7, 2026

Copy link
Copy Markdown

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

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown

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

coderabbitaiBot commented May 7, 2026

Copy link
Copy Markdown
Contributor

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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe 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✅ PassedThe 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✅ PassedAll 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✅ PassedNo 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.

@coderabbitaicoderabbitaiBot 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 (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

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown

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.

@KMKoushik
KMKoushik merged commit 31a49fb into usesend:mainMay 16, 2026
1 check passed
Sign up for freeto 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)

2 participants

@BenoitPrmt@KMKoushik
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Fix: Campaign subject is now interpolated with contact variables by BenoitPrmt · Pull Request #397 · usesend/useSend · GitHub
Skip to content

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

Merged
KMKoushik merged 2 commits into
usesend:mainfrom
BenoitPrmt:fix/campaign-subject-doesnt-have-variables
May 16, 2026
Merged

Fix: Campaign subject is now interpolated with contact variables#397
KMKoushik merged 2 commits into
usesend:mainfrom
BenoitPrmt:fix/campaign-subject-doesnt-have-variables

Conversation

@BenoitPrmt

@BenoitPrmtBenoitPrmt commented May 7, 2026

Copy link
Copy Markdown
Contributor

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

vercelBot commented May 7, 2026

Copy link
Copy Markdown

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

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown

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

coderabbitaiBot commented May 7, 2026

Copy link
Copy Markdown
Contributor

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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe 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✅ PassedThe 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✅ PassedAll 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✅ PassedNo 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.

@coderabbitaicoderabbitaiBot 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 (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

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown

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.

@KMKoushik
KMKoushik merged commit 31a49fb into usesend:mainMay 16, 2026
1 check passed
Sign up for freeto 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)

2 participants

@BenoitPrmt@KMKoushik
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Fix: Campaign subject is now interpolated with contact variables by BenoitPrmt · Pull Request #397 · usesend/useSend · GitHub
Skip to content

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

Merged
KMKoushik merged 2 commits into
usesend:mainfrom
BenoitPrmt:fix/campaign-subject-doesnt-have-variables
May 16, 2026
Merged

Fix: Campaign subject is now interpolated with contact variables#397
KMKoushik merged 2 commits into
usesend:mainfrom
BenoitPrmt:fix/campaign-subject-doesnt-have-variables

Conversation

@BenoitPrmt

@BenoitPrmtBenoitPrmt commented May 7, 2026

Copy link
Copy Markdown
Contributor

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

vercelBot commented May 7, 2026

Copy link
Copy Markdown

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

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown

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

coderabbitaiBot commented May 7, 2026

Copy link
Copy Markdown
Contributor

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 nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe 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✅ PassedThe 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✅ PassedAll 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✅ PassedNo 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.

@coderabbitaicoderabbitaiBot 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 (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

@greptile-appsgreptile-appsBot left a comment

Copy link
Copy Markdown

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.

@KMKoushik
KMKoushik merged commit 31a49fb into usesend:mainMay 16, 2026
1 check passed
Sign up for freeto 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)

2 participants

@BenoitPrmt@KMKoushik