Skip to content

Conversation

@sethconve
Copy link
Contributor

  • Introduced sendWithTemplate action to handle sending emails with templates.
  • Updated sendEmail function to accept both traditional and template-based email formats.
  • Enhanced validation to ensure either content or template is provided, but not both.
  • Added tests for template email functionality, including acceptance and rejection scenarios.
  • Updated schema and shared types to include template-related fields.

Fixes issue #44

@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 26, 2025

Open in StackBlitz

npm i https://pkg.pr.new/get-convex/resend/@convex-dev/resend@48

commit: f6e46f0

@coderabbitai
Copy link

coderabbitai bot commented Nov 26, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch seth/templates

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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

Comment on lines 43 to 44
templateId: v.optional(v.string()),
templateVariables: v.optional(v.string()),
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
templateId: v.optional(v.string()),
templateVariables: v.optional(v.string()),
template: v.optional(vTemplate),

Copy link
Contributor

@ianmacartney ianmacartney left a comment

Choose a reason for hiding this comment

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

other than the vTemplate change, lgtm

Comment on lines 663 to 669
if (email.templateId) {
payload.template = {
id: email.templateId,
variables: email.templateVariables
? JSON.parse(email.templateVariables)
: {},
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (email.templateId) {
payload.template = {
id: email.templateId,
variables: email.templateVariables
? JSON.parse(email.templateVariables)
: {},
};
if (email.template) {
payload.template = email.template;

Comment on lines +671 to +696
if (email.subject) {
payload.subject = email.subject;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

btw one way to set fields only if they exist is to use the pick helper from convex-helpers like payload = { ...pick(email, ["from", "bcc", "cc", "subject", "template"]

throw new Error("Either html or text must be provided");
// We require either html/text or a template to be provided.
const hasContent = args.html !== undefined || args.text !== undefined;
const hasTemplate = args.templateId !== undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const hasTemplate = args.templateId !== undefined;
const hasTemplate = args.template?.id !== undefined;

Comment on lines 103 to 104
templateId: v.optional(v.string()),
templateVariables: v.optional(v.string()),
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
templateId: v.optional(v.string()),
templateVariables: v.optional(v.string()),
template: v.optional(vTemplate),

Comment on lines 455 to 456
templateId?: string;
templateVariables?: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
templateId?: string;
templateVariables?: string;
template?: Template;

Comment on lines 306 to 342
if ("template" in sendEmailArgs) {
// Template-based email
const id = await ctx.runMutation(this.component.lib.sendEmail, {
options: await configToRuntimeConfig(this.config, this.onEmailEvent),
from: sendEmailArgs.from,
to:
typeof sendEmailArgs.to === "string"
? [sendEmailArgs.to]
: sendEmailArgs.to,
cc: toArray(sendEmailArgs.cc),
bcc: toArray(sendEmailArgs.bcc),
subject: sendEmailArgs.subject,
replyTo: sendEmailArgs.replyTo,
headers: sendEmailArgs.headers,
templateId: sendEmailArgs.template.id,
templateVariables: JSON.stringify(sendEmailArgs.template.variables),
});
return id as EmailId;
} else {
// Traditional email
const id = await ctx.runMutation(this.component.lib.sendEmail, {
options: await configToRuntimeConfig(this.config, this.onEmailEvent),
from: sendEmailArgs.from,
to:
typeof sendEmailArgs.to === "string"
? [sendEmailArgs.to]
: sendEmailArgs.to,
cc: toArray(sendEmailArgs.cc),
bcc: toArray(sendEmailArgs.bcc),
replyTo: sendEmailArgs.replyTo,
headers: sendEmailArgs.headers,
subject: sendEmailArgs.subject,
html: sendEmailArgs.html,
text: sendEmailArgs.text,
});
return id as EmailId;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm biased towards keeping both and adding template, since we'll be validating having the right fields anyways, and if they're specifying html and template it's probably better to fail than silently omit the html here, wdyt?

Comment on lines 306 to 342
if ("template" in sendEmailArgs) {
// Template-based email
const id = await ctx.runMutation(this.component.lib.sendEmail, {
options: await configToRuntimeConfig(this.config, this.onEmailEvent),
from: sendEmailArgs.from,
to:
typeof sendEmailArgs.to === "string"
? [sendEmailArgs.to]
: sendEmailArgs.to,
cc: toArray(sendEmailArgs.cc),
bcc: toArray(sendEmailArgs.bcc),
subject: sendEmailArgs.subject,
replyTo: sendEmailArgs.replyTo,
headers: sendEmailArgs.headers,
templateId: sendEmailArgs.template.id,
templateVariables: JSON.stringify(sendEmailArgs.template.variables),
});
return id as EmailId;
} else {
// Traditional email
const id = await ctx.runMutation(this.component.lib.sendEmail, {
options: await configToRuntimeConfig(this.config, this.onEmailEvent),
from: sendEmailArgs.from,
to:
typeof sendEmailArgs.to === "string"
? [sendEmailArgs.to]
: sendEmailArgs.to,
cc: toArray(sendEmailArgs.cc),
bcc: toArray(sendEmailArgs.bcc),
replyTo: sendEmailArgs.replyTo,
headers: sendEmailArgs.headers,
subject: sendEmailArgs.subject,
html: sendEmailArgs.html,
text: sendEmailArgs.text,
});
return id as EmailId;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if ("template" in sendEmailArgs) {
// Template-based email
const id = await ctx.runMutation(this.component.lib.sendEmail, {
options: await configToRuntimeConfig(this.config, this.onEmailEvent),
from: sendEmailArgs.from,
to:
typeof sendEmailArgs.to === "string"
? [sendEmailArgs.to]
: sendEmailArgs.to,
cc: toArray(sendEmailArgs.cc),
bcc: toArray(sendEmailArgs.bcc),
subject: sendEmailArgs.subject,
replyTo: sendEmailArgs.replyTo,
headers: sendEmailArgs.headers,
templateId: sendEmailArgs.template.id,
templateVariables: JSON.stringify(sendEmailArgs.template.variables),
});
return id as EmailId;
} else {
// Traditional email
const id = await ctx.runMutation(this.component.lib.sendEmail, {
options: await configToRuntimeConfig(this.config, this.onEmailEvent),
from: sendEmailArgs.from,
to:
typeof sendEmailArgs.to === "string"
? [sendEmailArgs.to]
: sendEmailArgs.to,
cc: toArray(sendEmailArgs.cc),
bcc: toArray(sendEmailArgs.bcc),
replyTo: sendEmailArgs.replyTo,
headers: sendEmailArgs.headers,
subject: sendEmailArgs.subject,
html: sendEmailArgs.html,
text: sendEmailArgs.text,
});
return id as EmailId;
}
options: await configToRuntimeConfig(this.config, this.onEmailEvent),
...sendEmailArgs,
to:
typeof sendEmailArgs.to === "string"
? [sendEmailArgs.to]
: sendEmailArgs.to,
cc: toArray(sendEmailArgs.cc),
bcc: toArray(sendEmailArgs.bcc),
});
return id as EmailId;

the only risk here is that passing extra args will fail -but in some ways that's a good thing

- Introduced `sendWithTemplate` action to handle sending emails with templates.
- Updated `sendEmail` function to accept both traditional and template-based email formats.
- Enhanced validation to ensure either content or template is provided, but not both.
- Added tests for template email functionality, including acceptance and rejection scenarios.
- Updated schema and shared types to include template-related fields.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants