Skip to main content

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

attendee_ids
array
required
Array of attendee UUIDs to send confirmation emails to. Must contain at least one valid UUID.
test_email_override
string
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)

results
array
Array of result objects for each attendee, indicating success or failure
sent
number
Total number of emails successfully sent
failed
number
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:
VariableDescriptionExample
{{name}}Attendee’s full nameJohn Smith
{{email}}Attendee’s email address[email protected]
{{unique_id}}Attendee’s unique check-in IDATT-12345
{{event_name}}Event nameTech Conference 2026
{{event_date}}Formatted event dateMarch 15, 2026
{{event_venue}}Event venueConvention Center
{{portal_url}}Personal attendee portal linkhttps://app.passtru.com/org/event/attendee/ATT-12345
{{qr_code_url}}QR code image URLhttps://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, "&amp;")
  .replace(/</g, "&lt;")
  .replace(/>/g, "&gt;")
  .replace(/"/g, "&quot;");

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

Build docs developers (and LLMs) love