Overview
The send-confirmation-email edge function sends event confirmation emails to attendees using customizable templates. Emails include attendee details, event information, QR codes, and personalized portal links. Supports bulk sending and test email functionality.
Endpoint
POST /functions/v1/send-confirmation-email
Authentication
Requires a valid Bearer token. The authenticated user must have access to the event (organization owner or assigned event manager).
Authorization: Bearer <supabase_access_token>
Request Body
Array of attendee UUIDs to send confirmation emails to. Must contain at least one valid UUID.
Override recipient email for testing. When provided, all emails are sent to this address instead of attendee emails.
Example Request
curl -X POST 'https://<project-ref>.supabase.co/functions/v1/send-confirmation-email' \
-H 'Authorization: Bearer <user_token>' \
-H 'Content-Type: application/json' \
-d '{
"attendee_ids": [
"abc12345-e89b-12d3-a456-426614174001",
"def67890-e89b-12d3-a456-426614174002"
]
}'
Example with Test Email
curl -X POST 'https://<project-ref>.supabase.co/functions/v1/send-confirmation-email' \
-H 'Authorization: Bearer <user_token>' \
-H 'Content-Type: application/json' \
-d '{
"attendee_ids": ["abc12345-e89b-12d3-a456-426614174001"],
"test_email_override": "[email protected]"
}'
Response
Success Response (200 OK)
Array of result objects for each attendee, indicating success or failure
Total number of emails successfully sent
Total number of emails that failed to send
{
"results": [
{
"id": "abc12345-e89b-12d3-a456-426614174001",
"success": true
},
{
"id": "def67890-e89b-12d3-a456-426614174002",
"success": false,
"error": "Invalid email address"
}
],
"sent": 1,
"failed": 1
}
Error Responses
400 Bad Request
Returned when request parameters are invalid.
{
"error": "attendee_ids array is required"
}
{
"error": "Invalid attendee_ids format"
}
{
"error": "Invalid test email address"
}
401 Unauthorized
Returned when authentication fails.
{
"error": "Unauthorized"
}
{
"error": "Unauthorized: no access to this event"
}
404 Not Found
Returned when attendees or event are not found.
{
"error": "No attendees found"
}
{
"error": "Event not found"
}
500 Internal Server Error
Returned when email sending fails or Resend API is not configured.
{
"error": "RESEND_API_KEY not configured"
}
{
"error": "Failed to send confirmation emails"
}
Email Template Variables
The confirmation email template supports the following variables:
| Variable | Description | Example |
|---|
{{name}} | Attendee’s full name | John Smith |
{{email}} | Attendee’s email address | [email protected] |
{{unique_id}} | Attendee’s unique check-in ID | ATT-12345 |
{{event_name}} | Event name | Tech Conference 2026 |
{{event_date}} | Formatted event date | March 15, 2026 |
{{event_venue}} | Event venue | Convention Center |
{{portal_url}} | Personal attendee portal link | https://app.passtru.com/org/event/attendee/ATT-12345 |
{{qr_code_url}} | QR code image URL | https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=ATT-12345 |
Template Processing
From source/supabase/functions/send-confirmation-email/index.ts:111-125:
// Build email content from template or default
let emailHtml = event.confirmation_email_content || getDefaultEmailTemplate();
// HTML-escape user data before template insertion to prevent injection
const esc = (s: string) => s
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """);
emailHtml = emailHtml
.replace(/\{\{name\}\}/gi, esc(attendee.name))
.replace(/\{\{email\}\}/gi, esc(attendee.email))
.replace(/\{\{unique_id\}\}/gi, esc(attendee.unique_id))
.replace(/\{\{event_name\}\}/gi, esc(event.name))
.replace(/\{\{event_date\}\}/gi, new Date(event.date).toLocaleDateString("en-MY", { dateStyle: "long" }))
.replace(/\{\{event_venue\}\}/gi, esc(event.venue || "TBA"))
.replace(/\{\{portal_url\}\}/gi, portalUrl)
.replace(/\{\{qr_code_url\}\}/gi, `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(attendee.unique_id)}`);
Default Email Template
From source/supabase/functions/send-confirmation-email/index.ts:192-203:
function getDefaultEmailTemplate(): string {
return `
<h2>Hi {{name}},</h2>
<p>You're confirmed for <strong>{{event_name}}</strong>!</p>
<p><strong>Date:</strong> {{event_date}}<br/><strong>Venue:</strong> {{event_venue}}</p>
<p>Your unique check-in ID is: <strong style="font-family: monospace; font-size: 18px; letter-spacing: 2px;">{{unique_id}}</strong></p>
<p>Show this QR code at the entrance for quick check-in:</p>
<p><img src="{{qr_code_url}}" alt="QR Code" width="200" height="200" /></p>
<p><a href="{{portal_url}}">View your attendee portal →</a></p>
<p>See you there!</p>
`;
}
Email Layout Wrapper
From source/supabase/functions/send-confirmation-email/index.ts:205-224:
function wrapInEmailLayout(content: string, eventName: string, orgName: string): string {
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body style="margin:0; padding:0; background-color:#f4f4f5; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
<div style="max-width:600px; margin:0 auto; padding:24px;">
<div style="background:#ffffff; border-radius:0; padding:32px; border:1px solid #e4e4e7;">
${content}
</div>
<div style="text-align:center; padding:16px; color:#71717a; font-size:12px;">
<p>${orgName} — Powered by PassTru</p>
</div>
</div>
</body>
</html>`;
}
Resend Integration
From source/supabase/functions/send-confirmation-email/index.ts:133-145:
const res = await fetch("https://api.resend.com/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${resendApiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: `PassTru <[email protected]>`,
to: [recipientEmail],
subject: `${test_email_override ? "[TEST] " : ""}Your Confirmation — ${event.name}`,
html: fullHtml,
}),
});
Audit Trail
From source/supabase/functions/send-confirmation-email/index.ts:166-177:
const successCount = results.filter((r) => r.success).length;
await adminClient.rpc("log_audit_event", {
_organization_id: event.organization_id,
_user_id: userId,
_action: "sent_confirmation_emails",
_entity_type: "event",
_entity_id: eventId,
_details: {
total: attendee_ids.length,
sent: successCount,
failed: attendee_ids.length - successCount,
},
});
Security Features
- HTML Escaping: All user-provided data is HTML-escaped before template insertion to prevent XSS
- UUID Validation: All attendee_ids validated as proper UUIDs
- Email Validation: Test email override validated for proper format
- Authorization Check: Verifies user has access to event (owner or assigned manager)
- Individual Error Handling: Each email sending attempt is try-caught separately
- Confirmation Tracking: Updates
confirmation_email_sent flag on successful sends
Use Cases
- Bulk Confirmation: Send confirmation emails to multiple attendees at once
- Post-Import Notifications: Send confirmations after CSV import
- Resend Confirmations: Re-send to attendees who didn’t receive original
- Test Email Preview: Preview email template with real data using test_email_override
- Custom Templates: Use custom Tiptap-created templates with rich formatting and images