Complete documentation for the Resend email system with domain management, template management, and delivery analytics.
Migration note (2026-03-11): Email provider migrated from Amazon SES to Resend. AWS SES secrets (
AWS_REGION,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,SES_CONFIGURATION_SET_NAME) have been removed. Replace withRESEND_API_KEYandRESEND_WEBHOOK_SECRET.
📚 Related Documentation:
- Campaign System - Email campaign management
- Deployment Guide - Supabase Secrets configuration
- API Endpoints - Complete API reference
- System Architecture - Platform overview
The Email System provides enterprise-grade email capabilities using Resend with comprehensive tracking, analytics, and template management. All email operations are handled through Supabase Edge Functions with the Resend API key stored securely as a Supabase Secret.
/admin/emails┌─────────────────────────────────────────────────────────────┐ │ FRONTEND (React) │ │ ├─ Email Service (src/services/email/emailService.ts) │ │ ├─ Admin Dashboard (/admin/emails) │ │ └─ React Email Templates (src/services/email/templates/) │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ SUPABASE EDGE FUNCTIONS (Deno) │ │ ├─ email-api (Send, Domain Mgmt, Analytics) │ │ └─ email-webhooks (Resend Event Processing) │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ RESEND (Email Service) │ │ ├─ Domain Verification (via Resend Dashboard) │ │ ├─ Email Sending (REST API) │ │ ├─ Bounce/Complaint Tracking │ │ └─ Webhook Events (Svix-signed) │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ DATABASE (PostgreSQL/Supabase) │ │ ├─ email_domains (Domain verification) │ │ ├─ email_templates (React Email templates) │ │ ├─ email_logs (Sent emails audit trail) │ │ ├─ email_events (Delivery events) │ │ └─ email_analytics (Daily statistics) │ └─────────────────────────────────────────────────────────────┘
Location: src/services/email/emailService.ts
Location: supabase/functions/
email-api - Main email API with actions:
send - Send emails with/without templates via Resendadd-domain - Register a domain in the databasemark-domain-verified - Mark domain verified after Resend dashboard confirmationdomains - List domains from databaselogs - Get email logsanalytics - Get email statisticsemail-webhooks - Resend webhook handler:
Location: src/services/email/templates/
BaseEmailTemplate.tsx - Base layout with brandingWelcomeEmail.tsx - Welcome email templateTransactionalEmail.tsx - Generic transactional templateindex.tsLocation: src/components/Admin/EmailManagement/
| Feature | Description | Status |
|---|---|---|
| Domain Management | DNS verification via Resend Dashboard | ✅ Active |
| Email Sending | Send via Resend REST API with templates or raw HTML | ✅ Active |
| Template Management | React Email components with variables | ✅ Active |
| Delivery Tracking | Track sent, delivered, bounced, complained | ✅ Active |
| Event Processing | Resend webhooks (Svix-signed) for real-time updates | ✅ Active |
| Analytics Dashboard | Delivery rates, charts, recommendations | ✅ Active |
| Email Logs | Complete audit trail with filtering | ✅ Active |
| Bounce Handling | Automatic bounce detection and logging | ✅ Active |
| Complaint Monitoring | Track spam complaints | ✅ Active |
| Test Email | Send test emails to verify configuration | ✅ Active |
| Multi-type Support | Transactional, marketing, notification | ✅ Active |
Endpoint: https://bgbavxtjlbvgplozizxu.supabase.co/functions/v1/email-api
Authentication: Requires Supabase anon or service_role key in Authorization header
All requests are made by invoking the email-api edge function with a body containing an action string. Available actions: send, add-domain, mark-domain-verified, domains, logs, analytics.
Send an email with or without a template.
Request parameters: to (string or array of recipient emails), subject, optional html body, optional text body, optional templateSlug, optional variables for template substitution, optional from email, optional fromName, optional cc and bcc arrays, optional emailType ('transactional', 'marketing', or 'notification'), optional tags, and optional metadata.
Response: success: true, messageId (Resend email UUID), logId (email_logs record ID).
Register a domain in the platform database (admin only). Verify the domain in the Resend Dashboard first, then mark it verified here.
Request parameters: action: 'add-domain', domain (e.g., 'example.com').
Response: success: true, domain object, message with instructions.
Mark a domain as verified after confirming DNS records in Resend Dashboard (admin only).
Request parameters: action: 'mark-domain-verified', domain.
Response: success: true.
Get email analytics for a date range.
Request parameters: action: 'analytics', optional dateRange object with start and end ISO date strings.
Response: success: true, totalSent, totalDelivered, totalBounced, totalComplained, totalOpened, totalClicked, deliveryRate (percentage), bounceRate (percentage), complaintRate (percentage), openRate (percentage), clickRate (percentage).
Endpoint: https://bgbavxtjlbvgplozizxu.supabase.co/functions/v1/email-webhooks
Purpose: Process Resend webhook events (delivery, bounce, complaint, open, click)
Authentication: Svix HMAC-SHA256 signature verification using RESEND_WEBHOOK_SECRET
Supported Events:
| Resend Event | Internal Type | Description |
|---|---|---|
email.sent |
send |
Accepted by Resend |
email.delivered |
delivery |
Delivered to recipient |
email.delivery_delayed |
delivery_delayed |
Temporary delay |
email.bounced |
bounce |
Permanent bounce |
email.complained |
complaint |
Spam complaint |
email.opened |
open |
Recipient opened |
email.clicked |
click |
Recipient clicked a link |
Auto-Processing:
email_logs status and timestampsemail_events recordsSetup:
email-webhooks edge functionhttps://bgbavxtjlbvgplozizxu.supabase.co/functions/v1/email-webhooksRESEND_WEBHOOK_SECRET in Supabase Edge Function secretsStores verified domains for sending emails.
| Column | Type | Description |
|---|---|---|
id |
UUID | Primary key |
domain |
TEXT | Domain name (e.g., 'materialkai.com') |
verification_status |
TEXT | 'pending', 'verified', 'failed' |
verification_token |
TEXT | DNS TXT record reference (verification done via Resend Dashboard) |
dkim_tokens |
TEXT[] | DKIM token values |
is_default |
BOOLEAN | Default domain for sending |
reputation_score |
INTEGER | Domain reputation (0-100) |
created_at |
TIMESTAMPTZ | Creation timestamp |
updated_at |
TIMESTAMPTZ | Last update timestamp |
Indexes: Unique on domain, Index on verification_status
RLS: Admin read/write, Service role full access
Stores email templates with React Email components.
| Column | Type | Description |
|---|---|---|
id |
UUID | Primary key |
slug |
TEXT | Unique template identifier |
name |
TEXT | Display name |
description |
TEXT | Template description |
subject_template |
TEXT | Subject with {{variables}} |
html_template |
TEXT | HTML body with {{variables}} |
text_template |
TEXT | Plain text version |
variables |
JSONB | Available variables schema |
is_active |
BOOLEAN | Template active status |
created_at |
TIMESTAMPTZ | Creation timestamp |
updated_at |
TIMESTAMPTZ | Last update timestamp |
Indexes: Unique on slug, Index on is_active
RLS: Admin read/write, Service role full access
Complete audit trail of all sent emails.
| Column | Type | Description |
|---|---|---|
id |
UUID | Primary key |
template_id |
UUID | Reference to email_templates |
domain_id |
UUID | Reference to email_domains |
message_id |
TEXT | Resend email ID (UUID) |
from_email |
TEXT | Sender email address |
from_name |
TEXT | Sender display name |
to_email |
TEXT | Recipient email address |
cc_emails |
TEXT[] | CC recipients |
bcc_emails |
TEXT[] | BCC recipients |
reply_to |
TEXT | Reply-to address |
subject |
TEXT | Email subject |
html_body |
TEXT | HTML email body |
text_body |
TEXT | Plain text body |
status |
TEXT | 'queued', 'sent', 'delivered', 'bounced', 'complained' |
email_type |
TEXT | 'transactional', 'marketing', 'notification' |
priority |
INTEGER | Priority (1-10, default 5) |
scheduled_at |
TIMESTAMPTZ | Scheduled send time |
sent_at |
TIMESTAMPTZ | Actual send time |
delivered_at |
TIMESTAMPTZ | Delivery timestamp |
bounced_at |
TIMESTAMPTZ | Bounce timestamp |
complained_at |
TIMESTAMPTZ | Complaint timestamp |
opened_at |
TIMESTAMPTZ | First open timestamp |
clicked_at |
TIMESTAMPTZ | First click timestamp |
error_message |
TEXT | Error details if failed |
tags |
JSONB | Custom tags for filtering |
variables |
JSONB | Template variables used |
metadata |
JSONB | Additional metadata |
created_at |
TIMESTAMPTZ | Creation timestamp |
updated_at |
TIMESTAMPTZ | Last update timestamp |
Indexes: On message_id, to_email, status, email_type, created_at
RLS: Admin read, Service role full access
Tracks all email delivery events from SES.
| Column | Type | Description |
|---|---|---|
id |
UUID | Primary key |
log_id |
UUID | Reference to email_logs |
event_type |
TEXT | 'bounce', 'complaint', 'delivery', 'delivery_delayed', 'send', 'open', 'click' |
event_data |
JSONB | Full event payload from Resend |
timestamp |
TIMESTAMPTZ | Event timestamp |
created_at |
TIMESTAMPTZ | Record creation timestamp |
Indexes: On log_id, event_type, timestamp
Triggers: Auto-update email_logs on event insert
RLS: Admin read, Service role full access
Daily aggregated email statistics.
| Column | Type | Description |
|---|---|---|
id |
UUID | Primary key |
date |
DATE | Analytics date |
domain_id |
UUID | Reference to email_domains |
total_sent |
INTEGER | Total emails sent |
total_delivered |
INTEGER | Total emails delivered |
total_bounced |
INTEGER | Total bounces |
total_complained |
INTEGER | Total complaints |
total_opened |
INTEGER | Total opens |
total_clicked |
INTEGER | Total clicks |
created_at |
TIMESTAMPTZ | Creation timestamp |
updated_at |
TIMESTAMPTZ | Last update timestamp |
Indexes: Unique on (date, domain_id), Index on date
RLS: Admin read, Service role full access
bgbavxtjlbvgplozizxu)material-kai-productionre_CRITICAL: The Resend API key MUST be stored as a Supabase Secret, NOT an environment variable.
bgbavxtjlbvgplozizxu)| Secret Name | Value | Description |
|---|---|---|
RESEND_API_KEY |
re_xxxxxxxxxxxxxxxxxxxx |
Resend API key for email sending |
RESEND_WEBHOOK_SECRET |
whsec_xxxxxxxxxxxxxxxxxxxx |
Resend webhook signing secret (add after Step 4) |
Why Supabase Secrets?
Note: Default sender email and name are configured through the Admin Panel at
/admin/email→ Email Settings, not as environment variables.
Route Supabase Auth emails (magic links, confirmations, password resets) through Resend:
| Field | Value |
|---|---|
| Host | smtp.resend.com |
| Port | 465 |
| Username | resend |
| Password | Your RESEND_API_KEY |
| Sender email | noreply@yourdomain.com |
| Sender name | Material KAI |
supabase functions deploy email-api
supabase functions deploy email-webhooks
supabase functions deploy email-webhook
https://bgbavxtjlbvgplozizxu.supabase.co/functions/v1/email-webhooksemail.sent, email.delivered, email.delivery_delayed, email.bounced, email.complained, email.opened, email.clickedwhsec_)RESEND_WEBHOOK_SECRET to Supabase Edge Function secrets (Step 2)/admin/emails in Material Kai → Domains tab/admin/emailsInvoke the email-api edge function with action: 'send', the recipient email, subject, templateSlug: 'welcome', the template variables (userName, loginUrl, supportEmail), and emailType: 'transactional'.
Invoke email-api with action: 'send', the customer email, an order confirmation subject, raw HTML body content, emailType: 'transactional', and custom tags containing the orderId and customerId.
Loop through a list of recipient emails, invoking email-api for each with action: 'send', the marketing template slug, per-recipient variables, emailType: 'marketing', and campaign tags. Implement rate limiting between sends (e.g., 100ms wait between each invocation).
Invoke email-api with action: 'analytics' and a dateRange object specifying start and end dates. The response contains deliveryRate, bounceRate, and complaintRate percentages.
Invoke email-api with action: 'sending-stats'. Use the returned sentLast24Hours and max24HourSend values to calculate and monitor your quota utilization percentage.
Access the admin dashboard at /admin/emails (requires admin role).
Features:
Metrics:
Recommendations:
Features:
Columns:
Features:
Domain Status:
Features:
Template Fields:
Features:
Usage:
| Practice | Description | Impact |
|---|---|---|
| Warm up domain | Start with 50 emails/day, double every 3 days | High |
| Monitor bounce rate | Keep below 5% | Critical |
| Monitor complaint rate | Keep below 0.1% | Critical |
| Use verified domains | Always send from verified domains | Critical |
| Implement DKIM | Add DKIM DNS records | High |
| Set up SPF | Configure SPF records | High |
| Use DMARC | Implement DMARC policy | Medium |
| Clean email lists | Remove invalid addresses regularly | High |
| Segment audiences | Send relevant content to each segment | Medium |
| Respect unsubscribes | Honor unsubscribe requests immediately | Critical |
| Practice | Description |
|---|---|
| Mobile-first | Design for mobile devices (60%+ opens) |
| Simple layouts | Avoid complex CSS and nested tables |
| Alt text | Add descriptive alt text to all images |
| Plain text version | Always include plain text alternative |
| Test thoroughly | Test in Gmail, Outlook, Apple Mail, etc. |
| Unsubscribe link | Include for all marketing emails |
| Preheader text | Add compelling preheader (50-100 chars) |
| CTA buttons | Use clear, action-oriented CTAs |
| Brand consistency | Match brand colors and fonts |
| Practice | Description | Priority |
|---|---|---|
| Use Supabase Secrets | Never expose Resend API key in frontend | Critical |
| Validate inputs | Sanitize email addresses and content | High |
| Rate limiting | Implement sending rate limits | High |
| Monitor abuse | Watch for unusual sending patterns | High |
| Use HTTPS | Always use HTTPS for webhooks | Critical |
| Verify webhook signatures | Validate Resend/Svix webhook signatures via RESEND_WEBHOOK_SECRET |
High |
| RLS policies | Enable Row Level Security on all tables | Critical |
| Audit logs | Track all email operations | Medium |
| Practice | Description |
|---|---|
| Batch processing | Process emails in batches of 10-50 |
| Async operations | Use async/await for all I/O operations |
| Connection pooling | Reuse SES client connections |
| Retry logic | Implement exponential backoff for retries |
| Queue management | Use job queues for high-volume sending |
| Monitor quotas | Track SES sending limits |
| Cache templates | Cache rendered templates |
Symptoms:
Solutions:
Check Supabase Secrets: Verify that RESEND_API_KEY is set — run supabase secrets list and confirm it appears.
Check Domain Verification:
/admin/emails → DomainsCheck Resend Dashboard: Log in to resend.com and confirm the domain is verified and the API key is active.
Check Edge Function Logs: Run supabase functions logs email-api to review function output.
Symptoms:
Solutions:
Validate Email Addresses: Use a regex or email validation library to check address format before sending.
Clean Email List:
Check Domain Reputation:
Review Bounce Types: Query the email_events table filtering by event_type 'bounce' and group by the bounceType field in the event_data JSON to identify patterns.
Symptoms:
Solutions:
Check Resend Webhook Registration:
Check RESEND_WEBHOOK_SECRET:
supabase secrets list to confirm the secret is setTest Webhook: Send a test event from the Resend dashboard webhook page to verify the endpoint responds with 200.
Check Edge Function Logs: Run supabase functions logs email-webhooks to review function output.
Symptoms:
Solutions:
Review Email Content:
Segment Audience:
Monitor Feedback Loop: Query the email_events table filtering by event_type 'complaint' and group by event_data to identify patterns.
Implement Unsubscribe:
Last Updated: 2026-03-11 Version: 2.0.0 (Resend migration) Maintainer: Material Kai Development Team