Resend Email Integration
Transactional email system for receipts, notifications, and session communications
Resend Email Integration
SavvySolve uses Resend for transactional emails. This integration provides reliable email delivery for receipts, session notifications, rating requests, and solver onboarding communications.
Architecture Overview
The email system consists of four main components working together to provide reliable, trackable email delivery:
- Resend Client (
lib/email/client.ts) - Singleton wrapper around the Resend SDK - React Email Templates (
lib/email/templates/) - Branded email templates using React Email - Send Utilities (
lib/email/send.ts) - Email sending functions with database logging - Webhook Handler (
app/api/webhooks/resend/route.ts) - Handles delivery status updates
All sent emails are logged to the email_logs table, enabling tracking of delivery status, bounce handling, and debugging.
Email Templates
The system includes five email templates, each built with React Email components and SavvySolve branding:
| Template | Purpose | When Sent |
|---|---|---|
ReceiptEmail | Payment confirmation | After successful payment |
RatingRequestEmail | Request session feedback | After session completion |
SolverAssignedEmail | Notify customer of solver | When solver claims ticket |
SessionCompletedEmail | Session summary + payment link | When session ends |
WelcomeSolverEmail | Onboarding for new solvers | When solver is approved |
Templates use the brand colors (Orange: #FF8A0D, Purple: #1B0F40) and Poppins font family for consistency with the application.
Configuration
Set these environment variables to enable email functionality:
# Required: Resend API key
RESEND_API_KEY="re_..."
# Optional: Custom sender (must be verified domain)
EMAIL_FROM="SavvySolve <noreply@savvysolve.io>"
# Optional: Reply-to address
EMAIL_REPLY_TO="support@savvysolve.io"
# Optional: Webhook secret for delivery tracking
RESEND_WEBHOOK_SECRET="whsec_..."Sending Emails
Using Convenience Functions
The simplest way to send emails is through the pre-built convenience functions:
import {
sendReceiptEmail,
sendRatingRequestEmail,
sendSolverAssignedEmail,
sendSessionCompletedEmail,
sendWelcomeSolverEmail,
} from "@/lib/email";
// Send a payment receipt
await sendReceiptEmail(
"customer@example.com",
{
customerName: "John Doe",
sessionDate: "January 15, 2025",
sessionDuration: "45 minutes",
solverName: "Alex",
problemSummary: "WiFi troubleshooting",
amount: "$45.00",
paymentId: "pi_123456",
receiptUrl: "https://savvysolve.io/receipt/123",
},
{ sessionId: "session-uuid" }
);
// Send session completion email with payment link
await sendSessionCompletedEmail(
"customer@example.com",
{
customerName: "Jane Smith",
solverName: "Mike",
sessionDate: "January 16, 2025",
sessionDuration: "30 minutes",
problemSummary: "Printer setup",
solutionNotes: "Installed drivers and configured network printing",
amount: "$35.00",
paymentUrl: "https://pay.stripe.com/...",
ratingUrl: "https://savvysolve.io/rate/abc123",
},
{ sessionId: "session-uuid", ticketId: "ticket-uuid" }
);Using the Generic sendEmail Function
For custom emails or more control:
import { sendEmail } from "@/lib/email";
import { ReceiptEmail } from "@/lib/email/templates";
const result = await sendEmail({
to: "customer@example.com",
subject: "Custom Subject Line",
template: ReceiptEmail({ customerName: "John", amount: "$50" }),
emailType: "receipt",
sessionId: "session-uuid",
metadata: { customField: "value" },
});
if (result.success) {
console.log("Email sent:", result.resendId);
} else {
console.error("Email failed:", result.error);
}Database Logging
Every email sent is logged to the email_logs table:
CREATE TABLE email_logs (
id UUID PRIMARY KEY,
session_id UUID REFERENCES sessions(id),
ticket_id UUID REFERENCES tickets(id),
resend_id VARCHAR(255),
email_type email_type NOT NULL, -- receipt, rating_request, etc.
"from" VARCHAR(255) NOT NULL,
"to" VARCHAR(255) NOT NULL,
subject VARCHAR(500) NOT NULL,
status email_status NOT NULL, -- pending, sent, delivered, bounced, complained, failed
error_message TEXT,
metadata JSONB,
sent_at TIMESTAMP WITH TIME ZONE,
delivered_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE,
updated_at TIMESTAMP WITH TIME ZONE
);Query email logs by session or ticket:
import { getEmailLogsBySession, getEmailLogsByTicket } from "@/lib/email";
// Get all emails for a session
const sessionEmails = await getEmailLogsBySession("session-uuid");
// Get all emails for a ticket
const ticketEmails = await getEmailLogsByTicket("ticket-uuid");Webhook Integration
The webhook handler at /api/webhooks/resend receives delivery status updates from Resend:
| Event Type | Updates Status To |
|---|---|
email.sent | sent |
email.delivered | delivered |
email.bounced | bounced |
email.complained | complained |
email.opened | (no status change) |
email.clicked | (no status change) |
Configure the webhook in Resend dashboard pointing to:
https://your-domain.com/api/webhooks/resendThe handler verifies webhook signatures when RESEND_WEBHOOK_SECRET is set.
Creating New Templates
To add a new email template:
- Create a new file in
lib/email/templates/:
import { Button, Section, Text } from "@react-email/components";
import * as React from "react";
import { BaseLayout, sharedStyles } from "./base-layout";
export interface NewTemplateEmailProps {
userName?: string;
actionUrl?: string;
}
export function NewTemplateEmail({
userName = "User",
actionUrl = "https://savvysolve.io",
}: NewTemplateEmailProps) {
return (
<BaseLayout preview="Preview text for inbox">
<Text style={sharedStyles.heading}>Email Heading</Text>
<Text style={sharedStyles.paragraph}>Hi {userName},</Text>
<Text style={sharedStyles.paragraph}>Your email content here.</Text>
<Section style={{ textAlign: "center", margin: "24px 0" }}>
<Button style={sharedStyles.button} href={actionUrl}>
Call to Action
</Button>
</Section>
</BaseLayout>
);
}
export default NewTemplateEmail;-
Export from
lib/email/templates/index.ts -
Add email type to
lib/db/schema/email-logs.ts:
export const emailTypeEnum = pgEnum("email_type", [
"receipt",
"rating_request",
// ... existing types
"new_template", // Add new type
]);-
Create convenience function in
lib/email/emails.ts -
Add tests in
lib/email/templates/templates.test.tsx
Testing
The email system has comprehensive test coverage:
- Client tests (
lib/email/client.test.ts) - Singleton behavior, env validation - Template tests (
lib/email/templates/templates.test.tsx) - Template rendering - Send tests (
lib/email/send.test.ts) - Sending flow, error handling - Webhook tests (
app/api/webhooks/resend/route.test.ts) - Status updates
Run tests with:
bun run test lib/email/
bun run test app/api/webhooks/resend/Email Preview
React Email provides a dev server for previewing templates:
bunx email devThis opens a browser interface where you can view and test all email templates with different props.