resend-integration

Complete guide for integrating Resend email services into Next.js applications with proper Audiences setup.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "resend-integration" with this command: npx skills add b-open-io/prompts/b-open-io-prompts-resend-integration

Resend Integration

Complete guide for integrating Resend email services into Next.js applications with proper Audiences setup.

When to Use

  • Setting up newsletter signups

  • Adding contact form email notifications

  • Implementing booking/calendar email confirmations

  • Configuring email forwarding via webhooks

  • Managing multi-domain Resend accounts

Resend Audiences Architecture

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

Multi-Domain Strategy

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" }], });

Implementation

  1. Shared Utility (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; } }

  1. Newsletter Route (/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: &#x3C;h2>Thanks for subscribing!&#x3C;/h2>..., });

return NextResponse.json({ success: true }); }

  1. Frontend Duplicate Handling

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!");

  1. Booking/Contact Form (Create Lead)

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));

  1. Inbound Email Forwarding

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: &#x3C;div style="padding: 16px; background: #f5f5f5;"> &#x3C;p>&#x3C;strong>From:&#x3C;/strong> ${event.data.from}&#x3C;/p> &#x3C;p>&#x3C;strong>To:&#x3C;/strong> ${event.data.to?.join(", ")}&#x3C;/p> &#x3C;/div> &#x3C;hr/> ${event.data.html || event.data.text} , attachments: event.data.attachments, }); break;

Environment Variables

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

Resend Dashboard Setup

IMPORTANT: Create these in the dashboard BEFORE deploying code that uses them.

Create Properties

Properties must exist before the API can use them.

  • Go to Audiences → Properties tab

  • Create these properties:

  • domain (text) - For multi-domain account filtering

  • source (text) - How contact signed up (newsletter, booking, contact)

  • company (text) - Optional company name

Create Segments

  • Go to Audiences → Segments

  • Create "project-newsletter" segment

  • Create "project-leads" segment

  • Copy IDs to env vars

Create Topics

  • Go to Audiences → Topics

  • Create topic (e.g., "Project Newsletter")

  • Defaults to: Opt-in (subscribers must explicitly opt in)

  • Visibility: Public (visible on preference page) or Private

  • Copy ID to env var

Email Receiving (Subdomain)

To receive emails without conflicting with existing email (e.g., Google Workspace):

DNS: Add MX record for subdomain

  • Name: mail

  • Content: inbound-smtp.us-east-1.amazonaws.com

  • Priority: 10

Resend: Enable receiving for mail.yourdomain.com

Webhook: Point to your /api/webhooks/resend endpoint

Broadcasts

Use Resend dashboard for sending newsletters:

  • Go to Broadcasts → Create

  • Select segment to target

  • Use personalization: {{{FIRST_NAME|there}}}

  • Include unsubscribe: {{{RESEND_UNSUBSCRIBE_URL}}}

  • Send or schedule

Common Patterns

Sender Addresses

Use consistent from addresses:

Team Notifications

Send internal notifications to a subdomain address that forwards:

to: ["info@mail.domain.com"] // Forwards via webhook

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

geo-optimizer

No summary provided by upstream source.

Repository SourceNeeds Review
General

npm-publish

No summary provided by upstream source.

Repository SourceNeeds Review
General

x-tweet-fetch

No summary provided by upstream source.

Repository SourceNeeds Review
General

x-tweet-search

No summary provided by upstream source.

Repository SourceNeeds Review