Set up Resend email integration with newsletters, contact forms, and booking systems. Use when implementing email functionality with Resend Audiences, segments, topics, webhooks, and multi-domain accounts.
Sets up Resend email integration for newsletters, contact forms, and booking systems. Use when implementing email functionality with Audiences, segments, topics, webhooks, or multi-domain accounts.
/plugin marketplace add b-open-io/prompts/plugin install bopen-tools@b-open-ioThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Complete guide for integrating Resend email services into Next.js applications with proper Audiences setup.
Resend has ONE audience per account. Use these features to organize:
| Feature | Purpose | Visibility |
|---|---|---|
| Contacts | Individual subscribers | - |
| Properties | Custom data fields (domain, source, company) | Internal |
| Segments | Internal groupings for targeting | Internal |
| Topics | User-facing email preferences | User can manage |
| Broadcasts | Campaign sending with auto-unsubscribe | - |
For accounts with multiple domains, tag contacts with properties:
await resend.contacts.create({
email,
properties: {
domain: "example.com", // Which project
source: "newsletter", // How they signed up
},
segments: [{ id: SEGMENT_ID }],
topics: [{ id: TOPIC_ID, subscription: "opt_in" }],
});
lib/resend.ts)import { Resend } from "resend";
export const resend = new Resend(process.env.RESEND_API_KEY);
const SEGMENT_NEWSLETTER = process.env.RESEND_SEGMENT_NEWSLETTER;
const SEGMENT_LEADS = process.env.RESEND_SEGMENT_LEADS;
const TOPIC_NEWSLETTER = process.env.RESEND_TOPIC_NEWSLETTER;
type ContactSource = "newsletter" | "booking" | "contact";
interface CreateContactOptions {
email: string;
firstName?: string;
lastName?: string;
company?: string;
source: ContactSource;
subscribeToNewsletter?: boolean;
}
export async function createContact({
email,
firstName,
lastName,
company,
source,
subscribeToNewsletter = false,
}: CreateContactOptions) {
const segments: { id: string }[] = [];
if (source === "newsletter" && SEGMENT_NEWSLETTER) {
segments.push({ id: SEGMENT_NEWSLETTER });
} else if ((source === "booking" || source === "contact") && SEGMENT_LEADS) {
segments.push({ id: SEGMENT_LEADS });
}
const topics: { id: string; subscription: "opt_in" | "opt_out" }[] = [];
if (subscribeToNewsletter && TOPIC_NEWSLETTER) {
topics.push({ id: TOPIC_NEWSLETTER, subscription: "opt_in" });
}
const properties: Record<string, string> = {
domain: "YOUR_DOMAIN.com", // Replace with actual domain
source,
};
if (company) properties.company = company;
const { data, error } = await resend.contacts.create({
email,
firstName: firstName || undefined,
lastName: lastName || undefined,
unsubscribed: false,
...(Object.keys(properties).length > 0 && { properties }),
...(segments.length > 0 && { segments }),
...(topics.length > 0 && { topics }),
});
if (error?.message?.includes("already exists")) {
return { exists: true, error: null };
}
return { data, exists: false, error };
}
export async function contactExists(email: string): Promise<boolean> {
try {
const { data } = await resend.contacts.get({ email });
return !!data;
} catch {
return false;
}
}
/api/newsletter)import { NextResponse } from "next/server";
import { resend, createContact, contactExists } from "@/lib/resend";
export async function POST(request: Request) {
const { email } = await request.json();
if (!email) {
return NextResponse.json({ error: "Email is required" }, { status: 400 });
}
// Duplicate check
if (await contactExists(email)) {
return NextResponse.json(
{ error: "already_subscribed", message: "You're already subscribed!" },
{ status: 409 },
);
}
const { error } = await createContact({
email,
source: "newsletter",
subscribeToNewsletter: true,
});
if (error) {
// Return actual error, not generic 500
const message = typeof error === "object" && "message" in error
? (error as { message: string }).message
: "Failed to subscribe";
const statusCode = typeof error === "object" && "statusCode" in error
? (error as { statusCode: number }).statusCode
: 500;
return NextResponse.json({ error: message }, { status: statusCode });
}
// Send welcome email
await resend.emails.send({
from: "Company <noreply@example.com>",
to: [email],
subject: "Welcome to our Newsletter",
html: `<h2>Thanks for subscribing!</h2>...`,
});
return NextResponse.json({ success: true });
}
const response = await fetch("/api/newsletter", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email }),
});
const data = await response.json();
if (response.status === 409) {
toast.info("You're already subscribed!");
return;
}
if (!response.ok) {
throw new Error(data.error);
}
toast.success("Thanks for subscribing!");
Add contact creation without blocking the main flow:
// In booking or contact form API route
createContact({
email,
firstName,
lastName,
company,
source: "booking", // or "contact"
}).catch((err) => console.error("Failed to create contact:", err));
For receiving emails via subdomain (e.g., mail.example.com):
Webhook handler (/api/webhooks/resend):
case "email.received":
const forwardTo = process.env.EMAIL_FORWARD_TO?.split(",").map(e => e.trim());
if (!forwardTo?.length) return;
await resend.emails.send({
from: "Forwarded <forwarded@example.com>",
to: forwardTo,
replyTo: event.data.from,
subject: `[Fwd] ${event.data.subject}`,
html: `
<div style="padding: 16px; background: #f5f5f5;">
<p><strong>From:</strong> ${event.data.from}</p>
<p><strong>To:</strong> ${event.data.to?.join(", ")}</p>
</div>
<hr/>
${event.data.html || event.data.text}
`,
attachments: event.data.attachments,
});
break;
# Required
RESEND_API_KEY=re_xxxxx
# Optional - for Audiences integration
RESEND_SEGMENT_NEWSLETTER=seg_xxxxx
RESEND_SEGMENT_LEADS=seg_xxxxx
RESEND_TOPIC_NEWSLETTER=top_xxxxx
# Optional - for email forwarding
EMAIL_FORWARD_TO=email1@example.com,email2@example.com
IMPORTANT: Create these in the dashboard BEFORE deploying code that uses them.
Properties must exist before the API can use them.
domain (text) - For multi-domain account filteringsource (text) - How contact signed up (newsletter, booking, contact)company (text) - Optional company nameTo receive emails without conflicting with existing email (e.g., Google Workspace):
DNS: Add MX record for subdomain
mailinbound-smtp.us-east-1.amazonaws.comResend: Enable receiving for mail.yourdomain.com
Webhook: Point to your /api/webhooks/resend endpoint
Use Resend dashboard for sending newsletters:
{{{FIRST_NAME|there}}}{{{RESEND_UNSUBSCRIBE_URL}}}Use consistent from addresses:
noreply@domain.com - Automated notificationscontact@domain.com - Contact formbooking@domain.com - Calendar invitesforwarded@domain.com - Forwarded inbound emailsSend internal notifications to a subdomain address that forwards:
to: ["info@mail.domain.com"] // Forwards via webhook
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.