saasprokit
Email

Email Templates

Pre-built React Email templates for common use cases

Template Overview

The email package includes 21 pre-built templates covering authentication, organization management, billing, and content notifications.

All templates are built with React Email components and support:

  • Responsive design
  • Light color scheme only (email-layout.tsx sets color-scheme / supported-color-schemes to light)
  • i18n (English, Spanish)
  • Consistent branding

Not all 21 are actually called from application code today. Auth, org-invitation, and subscription-lifecycle templates are triggered from Better Auth plugin hooks in packages/auth/server.ts. The rest (marked Unused below) are fully built but not currently invoked anywhere — wire them up where noted if you want that notification to actually go out.

Authentication & Security

Password Reset (wired — packages/auth/server.ts)

import { sendPasswordResetEmail } from '@repo/email';

await sendPasswordResetEmail({
  email: 'user@example.com',
  resetUrl: 'https://app.example.com/reset?token=abc123',
});
PropTypeDescription
emailstringRecipient email address
resetUrlstringPassword reset link

Email Verification (wired — packages/auth/server.ts)

import { sendEmailVerificationEmail } from '@repo/email';

await sendEmailVerificationEmail({
  email: 'user@example.com',
  userName: 'John',
  verificationUrl: 'https://app.example.com/verify?token=abc123',
});
PropTypeDescription
emailstringRecipient email address
userNamestring?User's display name
verificationUrlstringVerification link
import { sendMagicLinkEmail } from '@repo/email';

await sendMagicLinkEmail({
  email: 'user@example.com',
  magicLinkUrl: 'https://app.example.com/auth/magic-link?token=abc123',
  expiresInMinutes: 10,
});
PropTypeDescription
emailstringRecipient email address
magicLinkUrlstringThe one-time sign-in link
expiresInMinutesnumber?Link expiration time (default: 10)

Sign-In OTP (wired — packages/auth/server.ts)

import { sendSignInOTPEmail } from '@repo/email';

await sendSignInOTPEmail({
  email: 'user@example.com',
  otp: '123456',
  expiresInMinutes: 5,
});
PropTypeDescription
emailstringRecipient email address
otpstringOne-time passcode for the email-OTP sign-in flow
expiresInMinutesnumber?OTP expiration time (default: 5)

Verification OTP (wired — packages/auth/server.ts)

import { sendVerificationOTPEmail } from '@repo/email';

await sendVerificationOTPEmail({
  email: 'user@example.com',
  otp: '123456',
  expiresInMinutes: 5,
});
PropTypeDescription
emailstringRecipient email address
otpstringOne-time passcode used for email verification
expiresInMinutesnumber?OTP expiration time (default: 5)

Two-Factor OTP (wired — packages/auth/server.ts)

import { sendTwoFactorOTPEmail } from '@repo/email';

await sendTwoFactorOTPEmail({
  email: 'user@example.com',
  otp: '123456',
  expiresInMinutes: 5,
});
PropTypeDescription
emailstringRecipient email address
otpstringOne-time password code
expiresInMinutesnumber?OTP expiration time (default: 5)

Login from New Device (not currently used)

import { sendLoginNewDeviceEmail } from '@repo/email';

await sendLoginNewDeviceEmail({
  email: 'user@example.com',
  userName: 'John',
  deviceInfo: 'Chrome on macOS',
  location: 'San Francisco, CA',
  ipAddress: '192.168.1.1',
  loginTime: '2024-01-15 10:30 AM',
  secureAccountUrl: 'https://app.example.com/security',
});

Password Changed (not currently used)

import { sendPasswordChangedEmail } from '@repo/email';

await sendPasswordChangedEmail({
  email: 'user@example.com',
  userName: 'John',
  changedAt: '2024-01-15 10:30 AM',
  ipAddress: '192.168.1.1',
  secureAccountUrl: 'https://app.example.com/security',
});

Account Locked (not currently used)

import { sendAccountLockedEmail } from '@repo/email';

await sendAccountLockedEmail({
  email: 'user@example.com',
  userName: 'John',
  lockReason: 'Too many failed login attempts',
  failedAttempts: 5,
  lockedAt: '2024-01-15 10:30 AM',
  unlockTime: '2024-01-15 11:00 AM',
  resetPasswordUrl: 'https://app.example.com/reset-password',
});

Account Deleted (not currently used)

import { sendAccountDeletedEmail } from '@repo/email';

await sendAccountDeletedEmail({
  email: 'user@example.com',
  userName: 'John',
  deletedAt: '2024-01-15 10:30 AM',
  supportEmail: 'support@example.com',
});

Organization Management

Organization Invitation (wired — packages/auth/server.ts)

import { sendOrgInvitationEmail } from '@repo/email';

await sendOrgInvitationEmail({
  email: 'newuser@example.com',
  inviterName: 'John Doe',
  organizationName: 'Acme Corp',
  role: 'Editor',
  inviteUrl: 'https://app.example.com/invite?token=abc123',
  expiresAt: '2024-01-22',
  message: 'Looking forward to working with you!',
});

Invitation Accepted (not currently used)

import { sendInvitationAcceptedEmail } from '@repo/email';

await sendInvitationAcceptedEmail({
  email: 'owner@example.com',
  ownerName: 'John',
  newMemberName: 'Jane Smith',
  newMemberEmail: 'jane@example.com',
  organizationName: 'Acme Corp',
  role: 'Editor',
  joinedAt: '2024-01-15 10:30 AM',
  teamUrl: 'https://app.example.com/team',
});

Role Changed (not currently used)

import { sendRoleChangedEmail } from '@repo/email';

await sendRoleChangedEmail({
  email: 'user@example.com',
  userName: 'Jane',
  organizationName: 'Acme Corp',
  previousRole: 'Viewer',
  newRole: 'Editor',
  changedBy: 'John Doe',
  changedAt: '2024-01-15 10:30 AM',
  dashboardUrl: 'https://app.example.com/dashboard',
});

Removed from Organization (not currently used)

import { sendRemovedFromOrgEmail } from '@repo/email';

await sendRemovedFromOrgEmail({
  email: 'user@example.com',
  userName: 'Jane',
  organizationName: 'Acme Corp',
  removedBy: 'John Doe',
  removedAt: '2024-01-15 10:30 AM',
  supportEmail: 'support@example.com',
});

Subscription & Billing

Subscription Started (wired — packages/auth/stripe-hooks.ts)

import { sendSubscriptionStartedEmail } from '@repo/email';

await sendSubscriptionStartedEmail({
  email: 'user@example.com',
  userName: 'John',
  planName: 'Pro',
  billingInterval: 'monthly',
  amount: '$29.00',
  nextBillingDate: '2024-02-15',
  dashboardUrl: 'https://app.example.com/dashboard',
});

Payment Receipt (not currently used)

import { sendPaymentReceiptEmail } from '@repo/email';

await sendPaymentReceiptEmail({
  email: 'user@example.com',
  userName: 'John',
  invoiceNumber: 'INV-2024-001',
  amount: '$29.00',
  planName: 'Pro',
  billingPeriod: 'Jan 15 - Feb 14, 2024',
  paymentMethod: 'Visa ending in 4242',
  paymentDate: '2024-01-15',
  receiptUrl: 'https://app.example.com/receipts/INV-2024-001',
});

Subscription Upgraded (wired — packages/auth/stripe-hooks.ts)

import { sendSubscriptionUpgradedEmail } from '@repo/email';

await sendSubscriptionUpgradedEmail({
  email: 'user@example.com',
  userName: 'John',
  previousPlan: 'Basic',
  newPlan: 'Pro',
  newAmount: '$29.00',
  billingInterval: 'monthly',
  effectiveDate: '2024-01-15',
  dashboardUrl: 'https://app.example.com/dashboard',
});

Subscription Downgraded (wired — packages/auth/stripe-hooks.ts)

import { sendSubscriptionDowngradedEmail } from '@repo/email';

await sendSubscriptionDowngradedEmail({
  email: 'user@example.com',
  userName: 'John',
  previousPlan: 'Pro',
  newPlan: 'Basic',
  newAmount: '$9.00',
  billingInterval: 'monthly',
  effectiveDate: '2024-02-15',
  lostFeatures: ['Priority support', 'Advanced analytics', 'API access'],
  dashboardUrl: 'https://app.example.com/dashboard',
});

Subscription Cancelled (wired — packages/auth/stripe-hooks.ts)

import { sendSubscriptionCancelledEmail } from '@repo/email';

await sendSubscriptionCancelledEmail({
  email: 'user@example.com',
  userName: 'John',
  planName: 'Pro',
  accessUntil: '2024-02-15',
  cancellationReason: 'User requested',
  reactivateUrl: 'https://app.example.com/reactivate',
  feedbackUrl: 'https://app.example.com/feedback',
});

Content Notifications

Welcome Email (not currently used)

import { sendWelcomeEmail } from '@repo/email';

await sendWelcomeEmail({
  email: 'user@example.com',
  name: 'John',
});

Article Published (not currently used)

import { sendArticlePublishedEmail } from '@repo/email';

await sendArticlePublishedEmail({
  email: 'author@example.com',
  userName: 'John',
  articleTitle: 'Getting Started with React',
  articleExcerpt: 'Learn the basics of React in this comprehensive guide...',
  articleUrl: 'https://blog.example.com/getting-started-react',
  coverImageUrl: 'https://blog.example.com/images/react-cover.jpg',
  publishedAt: '2024-01-15 10:30 AM',
  dashboardUrl: 'https://app.example.com/dashboard',
  analyticsUrl: 'https://app.example.com/analytics',
});

Previewing Templates

The canonical way to preview templates is the dedicated apps/email app (see Getting Started):

pnpm --filter email dev

Open http://localhost:3003 to view all templates in the browser. (packages/email also has its own pnpm dev script pointing at the same templates/ directory on port 3030 — either works, but apps/email is the one referenced elsewhere in these docs.)

Creating Custom Templates

  1. Create a new file in packages/email/templates/:
// templates/my-custom-email.tsx
import { EmailLayout, Heading, EmailText, EmailButton } from '../components';

interface MyCustomEmailProps {
  userName?: string;
  actionUrl?: string;
}

export default function MyCustomEmail({
  userName = 'there',
  actionUrl = 'https://example.com',
}: MyCustomEmailProps) {
  return (
    <EmailLayout preview="Your custom email preview">
      <Heading>Hello, {userName}!</Heading>
      <EmailText>This is your custom email content.</EmailText>
      <EmailButton href={actionUrl}>Take Action</EmailButton>
    </EmailLayout>
  );
}
  1. Add the send function in index.ts, using the package's internal sendEmail() helper (it wraps getEmailProvider() so individual send functions don't touch provider details directly):
import MyCustomEmail from './templates/my-custom-email';

export async function sendMyCustomEmail({
  email,
  userName,
  actionUrl,
}: {
  email: string;
  userName?: string;
  actionUrl?: string;
}) {
  const result = await sendEmail({
    to: email,
    subject: 'Your Custom Subject',
    react: MyCustomEmail({ userName, actionUrl }),
  });

  if (!result.success) {
    throw result.error || new Error('Failed to send email');
  }

  return { success: true, messageId: result.messageId };
}

Always use the shared components from ../components to maintain consistent styling across all templates.

On this page