SavvySolve Docs

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:

  1. Resend Client (lib/email/client.ts) - Singleton wrapper around the Resend SDK
  2. React Email Templates (lib/email/templates/) - Branded email templates using React Email
  3. Send Utilities (lib/email/send.ts) - Email sending functions with database logging
  4. 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:

TemplatePurposeWhen Sent
ReceiptEmailPayment confirmationAfter successful payment
RatingRequestEmailRequest session feedbackAfter session completion
SolverAssignedEmailNotify customer of solverWhen solver claims ticket
SessionCompletedEmailSession summary + payment linkWhen session ends
WelcomeSolverEmailOnboarding for new solversWhen 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:

lib/email/emails.ts
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:

lib/email/send.ts
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 TypeUpdates Status To
email.sentsent
email.delivereddelivered
email.bouncedbounced
email.complainedcomplained
email.opened(no status change)
email.clicked(no status change)

Configure the webhook in Resend dashboard pointing to:

https://your-domain.com/api/webhooks/resend

The handler verifies webhook signatures when RESEND_WEBHOOK_SECRET is set.

Creating New Templates

To add a new email template:

  1. Create a new file in lib/email/templates/:
lib/email/templates/new-template.tsx
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;
  1. Export from lib/email/templates/index.ts

  2. 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
]);
  1. Create convenience function in lib/email/emails.ts

  2. 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 dev

This opens a browser interface where you can view and test all email templates with different props.

On this page