Setup and context
Transactional emails — welcome messages, payment confirmations, password resets — are a critical part of any web application. Yet building HTML emails remains one of the most frustrating tasks in web development, thanks to inconsistent rendering across email clients and the need for table-based layouts.
In this guide, we'll combine Antigravity IDE's AI-powered development features with Resend (a developer-first email API) and React Email (a component library for building emails with React) to create a type-safe, maintainable email delivery system.
By leveraging Antigravity's agent capabilities, you can generate email templates, implement API routes, and test delivery — all within a streamlined AI-assisted workflow.
What Are Resend and React Email?
Resend
Resend is a modern email delivery API built for developers. Compared to legacy services like SendGrid or Mailgun, it offers several advantages:
- Simple API: Send emails instantly via REST API or official SDKs (Node.js, Python, Go, Ruby)
- React Email integration: Write templates in JSX and send them directly
- High deliverability: Automatic SPF, DKIM, and DMARC configuration
- Real-time analytics: Track open rates, click rates, and bounces from the dashboard
React Email
React Email lets you build email templates using React components. It abstracts away the quirks of HTML email — table layouts, client-specific hacks, and inline styles — behind a clean component API.
// Basic React Email component example
import { Html, Head, Body, Container, Text } from "@react-email/components";
// Each component generates email-client-compatible HTML
// <Container> → centered <table> layout
// <Text> → Outlook-compatible <p> tagsSetting Up Your Environment
Prerequisites
- Node.js 20 or later
- Antigravity IDE (latest version)
- Resend account (free tier: 3,000 emails/month)
- Next.js project (App Router recommended)
Installing Packages
Open the Antigravity terminal and run:
# Install Resend SDK and React Email components
npm install resend @react-email/components
# Install the dev preview server
npm install -D react-email
# Expected output:
# added 42 packages in 8sEnvironment Variables
Add your Resend API key to .env.local in your project root:
# .env.local
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxxxxxxxxxxTip: Use Antigravity's inline chat (
Cmd+I) and type "Add a Resend API key entry to.env.local" — the agent will add it in the correct format automatically.
Building Email Templates
Directory Structure
src/
├── emails/ # Email templates
│ ├── welcome.tsx # Welcome email
│ ├── payment-confirm.tsx # Payment confirmation
│ └── password-reset.tsx # Password reset
├── lib/
│ └── email.ts # Resend client setup
└── app/
└── api/
└── send-email/
└── route.ts # Email sending API
Welcome Email Template
Ask Antigravity's agent to "Create a welcome email template using React Email," and it will generate something like this:
// src/emails/welcome.tsx
import {
Html,
Head,
Preview,
Body,
Container,
Section,
Text,
Button,
Img,
Hr,
} from "@react-email/components";
interface WelcomeEmailProps {
userName: string;
loginUrl: string;
}
export default function WelcomeEmail({
userName = "User",
loginUrl = "https://example.com/login",
}: WelcomeEmailProps) {
return (
<Html lang="en">
<Head />
{/* Preview text shown next to the subject line in inboxes */}
<Preview>Welcome aboard, {userName}!</Preview>
<Body style={main}>
<Container style={container}>
<Img
src="https://example.com/logo.png"
width={48}
height={48}
alt="Logo"
/>
<Text style={heading}>Welcome, {userName}!</Text>
<Text style={paragraph}>
Your account has been created successfully. Log in now
to explore all the features available to you.
</Text>
<Section style={buttonContainer}>
<Button style={button} href={loginUrl}>
Log In
</Button>
</Section>
<Hr style={hr} />
<Text style={footer}>
If you didn't create this account, you can safely ignore this email.
</Text>
</Container>
</Body>
</Html>
);
}
// Style definitions (inline styles are required for email)
const main = {
backgroundColor: "#f6f9fc",
fontFamily: '-apple-system, "Segoe UI", Roboto, sans-serif',
};
const container = {
margin: "0 auto",
padding: "40px 20px",
maxWidth: "560px",
};
const heading = {
fontSize: "24px",
fontWeight: "bold" as const,
color: "#1a1a1a",
marginBottom: "16px",
};
const paragraph = {
fontSize: "16px",
lineHeight: "1.6",
color: "#4a4a4a",
};
const buttonContainer = { textAlign: "center" as const, margin: "32px 0" };
const button = {
backgroundColor: "#4f46e5",
color: "#ffffff",
padding: "12px 32px",
borderRadius: "8px",
fontSize: "16px",
textDecoration: "none",
};
const hr = { borderColor: "#e5e7eb", margin: "24px 0" };
const footer = { fontSize: "12px", color: "#9ca3af" };Payment Confirmation Template
// src/emails/payment-confirm.tsx
import {
Html,
Head,
Preview,
Body,
Container,
Section,
Row,
Column,
Text,
Hr,
} from "@react-email/components";
interface PaymentConfirmProps {
userName: string;
planName: string;
amount: string;
currency: string;
receiptUrl: string;
}
export default function PaymentConfirmEmail({
userName = "User",
planName = "Pro Plan",
amount = "$3",
currency = "USD",
receiptUrl = "#",
}: PaymentConfirmProps) {
return (
<Html lang="en">
<Head />
<Preview>Payment confirmed: {planName} — {amount}</Preview>
<Body style={main}>
<Container style={container}>
<Text style={heading}>Payment Confirmed</Text>
<Text style={paragraph}>
Hi {userName}, your payment for the {planName} has been
successfully processed.
</Text>
<Section style={receiptBox}>
<Row>
<Column><Text style={label}>Plan</Text></Column>
<Column><Text style={value}>{planName}</Text></Column>
</Row>
<Hr style={hr} />
<Row>
<Column><Text style={label}>Amount</Text></Column>
<Column><Text style={value}>{amount} ({currency})</Text></Column>
</Row>
</Section>
<Text style={footerText}>
You can download your receipt <a href={receiptUrl} style={link}>here</a>.
</Text>
</Container>
</Body>
</Html>
);
}
const main = {
backgroundColor: "#f6f9fc",
fontFamily: '-apple-system, "Segoe UI", Roboto, sans-serif',
};
const container = { margin: "0 auto", padding: "40px 20px", maxWidth: "560px" };
const heading = { fontSize: "24px", fontWeight: "bold" as const, color: "#1a1a1a" };
const paragraph = { fontSize: "16px", lineHeight: "1.6", color: "#4a4a4a" };
const receiptBox = {
backgroundColor: "#ffffff",
border: "1px solid #e5e7eb",
borderRadius: "8px",
padding: "24px",
margin: "24px 0",
};
const label = { fontSize: "14px", color: "#6b7280" };
const value = { fontSize: "14px", fontWeight: "bold" as const, textAlign: "right" as const };
const hr = { borderColor: "#e5e7eb" };
const footerText = { fontSize: "14px", color: "#6b7280" };
const link = { color: "#4f46e5" };Resend Client and API Implementation
Resend Client Setup
// src/lib/email.ts
import { Resend } from "resend";
// Initialize Resend client as a singleton
// Fail early if the API key is missing
if (!process.env.RESEND_API_KEY) {
throw new Error("RESEND_API_KEY is not set");
}
export const resend = new Resend(process.env.RESEND_API_KEY);Email Sending API Route
// src/app/api/send-email/route.ts
import { NextRequest, NextResponse } from "next/server";
import { resend } from "@/lib/email";
import WelcomeEmail from "@/emails/welcome";
import PaymentConfirmEmail from "@/emails/payment-confirm";
// Map email types to their templates
const EMAIL_TEMPLATES = {
welcome: {
subject: "Welcome to MyApp!",
component: WelcomeEmail,
},
"payment-confirm": {
subject: "Payment Confirmation",
component: PaymentConfirmEmail,
},
} as const;
type EmailType = keyof typeof EMAIL_TEMPLATES;
export async function POST(req: NextRequest) {
try {
const { type, to, props } = await req.json();
// Validate the request
if (!type || !to || !EMAIL_TEMPLATES[type as EmailType]) {
return NextResponse.json(
{ error: "Invalid request: type and to are required" },
{ status: 400 }
);
}
const template = EMAIL_TEMPLATES[type as EmailType];
const Component = template.component;
// Send the email via Resend API
const { data, error } = await resend.emails.send({
from: "MyApp <noreply@yourdomain.com>",
to: [to],
subject: template.subject,
react: Component(props || {}),
});
if (error) {
console.error("Email send error:", error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
// Expected output: { id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }
return NextResponse.json({ success: true, id: data?.id });
} catch (err) {
console.error("Unexpected error:", err);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}Supercharging Development with Antigravity Agents
The real power of Antigravity IDE lies in its agent-driven development workflow. Email template creation is an area where this shines particularly bright.
Generating Templates Automatically
In Antigravity's chat panel, you can prompt the agent with specific requirements:
Create a password reset email template using React Email.
- Center a reset link button
- Note that the link expires in 1 hour
- Brand color: #4f46e5
- Include a security notice footer
The agent understands React Email's component API, so it produces correct, email-client-compatible HTML every time.
Preview and Testing
React Email ships with a built-in dev preview server:
# Start the preview server
npx react-email dev --dir src/emails --port 3001
# Expected output:
# Ready on http://localhost:3001
# Found 3 email templatesOpen http://localhost:3001 in your browser to see a list of all templates. You can preview each email and dynamically adjust props in real time.
Test Delivery with Resend
During development, use Resend's test mode to verify API behavior without sending actual emails:
# Test send via curl
curl -X POST http://localhost:3000/api/send-email \
-H "Content-Type: application/json" \
-d '{
"type": "welcome",
"to": "test@example.com",
"props": {
"userName": "Jane Doe",
"loginUrl": "https://example.com/login"
}
}'
# Expected output:
# {"success":true,"id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}Automated Email After Stripe Payments
One of the most common use cases for transactional email is sending payment confirmations after Stripe checkout. Combining Stripe webhooks with Resend makes this fully automatic. For more details on Stripe integration, see "Stripe × Antigravity Payment Implementation Guide."
// src/app/api/webhook/route.ts (excerpt)
import { resend } from "@/lib/email";
import PaymentConfirmEmail from "@/emails/payment-confirm";
// Stripe Webhook: send confirmation email on checkout.session.completed
async function handleCheckoutCompleted(session: any) {
const email = session.customer_details?.email;
if (!email) return;
await resend.emails.send({
from: "MyApp <noreply@yourdomain.com>",
to: [email],
subject: "Payment Confirmation — MyApp",
react: PaymentConfirmEmail({
userName: session.customer_details.name || "Customer",
planName: session.metadata?.plan_type === "pro" ? "Pro Plan" : "Premium Plan",
amount: `$${(session.amount_total / 100).toFixed(2)}`,
currency: session.currency?.toUpperCase() || "USD",
receiptUrl: session.receipt_url || "#",
}),
});
}Deploying to Production
DNS Configuration for Custom Domain Sending
To send emails from your own domain with Resend, add these DNS records:
# Values displayed in Resend Dashboard → Domains → Add Domain
TXT _resend re_xxxx... # Domain verification
CNAME send._domainkey ... # DKIM signing
TXT _dmarc v=DMARC1; p=none; ... # DMARC policy
Notes for Cloudflare Workers
When using Resend in a Cloudflare Workers environment, the SDK uses the fetch-based HTTP client, so no special configuration is needed. Just make sure to set your API key with wrangler secret put RESEND_API_KEY.
For more on building serverless APIs, check out "Antigravity × Hono + Cloudflare Workers Serverless API Guide."
Summary
Combining Antigravity IDE with Resend and React Email transforms transactional email development from a tedious chore into a streamlined, AI-assisted workflow. The key takeaways are: component-based template design with React Email, simple delivery logic via Resend's API, full automation through Stripe webhook integration, and accelerated template generation using Antigravity's agents.