Overview
The Email API allows you to send confirmation emails to attendees with personalized content, QR codes, and portal links. Emails are sent via the Resend service using edge functions.
Send Confirmation Emails
Send confirmation emails to one or more attendees:
const { data , error } = await supabase . functions . invoke ( 'send-confirmation-email' , {
body: {
attendee_ids: [
'123e4567-e89b-12d3-a456-426614174001' ,
'456e7890-e89b-12d3-a456-426614174002'
]
}
})
if ( error ) {
console . error ( 'Error sending emails:' , error )
} else {
console . log ( 'Emails sent:' , data . sent )
console . log ( 'Emails failed:' , data . failed )
}
Request Body
Response
{
"results" : [
{
"id" : "123e4567-e89b-12d3-a456-426614174001" ,
"success" : true
},
{
"id" : "456e7890-e89b-12d3-a456-426614174002" ,
"success" : true
}
],
"sent" : 2 ,
"failed" : 0
}
Array of result objects for each attendee Whether the email was sent successfully
Error message if success is false
Total number of emails successfully sent
Total number of emails that failed to send
Test Emails
Send a test email to verify your template before sending to attendees:
const { data , error } = await supabase . functions . invoke ( 'send-confirmation-email' , {
body: {
attendee_ids: [ '123e4567-e89b-12d3-a456-426614174001' ],
test_email_override: '[email protected] '
}
})
if ( ! error ) {
console . log ( 'Test email sent to [email protected] ' )
}
When test_email_override is provided, all emails will be sent to that address instead of the attendee’s actual email. The subject line will be prefixed with “[TEST]”.
Email Template Variables
Customize email content using template variables in your event’s confirmation_email_content:
Email Template Example
< 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}}" style = "display: inline-block; padding: 12px 24px; background: #0066cc; color: white; text-decoration: none; border-radius: 4px;" >
View Your Attendee Portal →
</ a >
</ p >
< p > See you there! </ p >
Set Email Template
Update the email template for an event:
const emailTemplate = `
<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>
`
const { error } = await supabase
. from ( 'events' )
. update ({ confirmation_email_content: emailTemplate })
. eq ( 'id' , eventId )
if ( error ) {
console . error ( 'Error updating template:' , error )
} else {
console . log ( 'Email template updated' )
}
Bulk Email Sending
Send emails to all attendees who haven’t received one:
// Get attendees without confirmation emails
const { data : attendees } = await supabase
. from ( 'attendees' )
. select ( 'id' )
. eq ( 'event_id' , eventId )
. eq ( 'confirmation_email_sent' , false )
if ( attendees && attendees . length > 0 ) {
const attendeeIds = attendees . map ( a => a . id )
// Send emails in batches of 50
const batchSize = 50
for ( let i = 0 ; i < attendeeIds . length ; i += batchSize ) {
const batch = attendeeIds . slice ( i , i + batchSize )
const { data , error } = await supabase . functions . invoke ( 'send-confirmation-email' , {
body: { attendee_ids: batch }
})
if ( error ) {
console . error ( `Batch ${ i / batchSize + 1 } failed:` , error )
} else {
console . log ( `Batch ${ i / batchSize + 1 } : ${ data . sent } sent, ${ data . failed } failed` )
}
// Wait 1 second between batches to avoid rate limits
if ( i + batchSize < attendeeIds . length ) {
await new Promise ( resolve => setTimeout ( resolve , 1000 ))
}
}
}
Email Status Tracking
Check which attendees have received confirmation emails:
const { data : stats } = await supabase
. from ( 'attendees' )
. select ( 'confirmation_email_sent' )
. eq ( 'event_id' , eventId )
if ( stats ) {
const sent = stats . filter ( a => a . confirmation_email_sent ). length
const pending = stats . length - sent
console . log ( `Emails sent: ${ sent } / ${ stats . length } ` )
console . log ( `Pending: ${ pending } ` )
}
Get Attendees Without Emails
Retrieve attendees who haven’t received confirmation emails:
const { data : pending } = await supabase
. from ( 'attendees' )
. select ( 'id, name, email, unique_id' )
. eq ( 'event_id' , eventId )
. eq ( 'confirmation_email_sent' , false )
if ( pending ) {
console . log ( ` ${ pending . length } attendees pending email` )
pending . forEach ( a => {
console . log ( ` ${ a . name } ( ${ a . email } )` )
})
}
Email Audit Logging
The send-confirmation-email function automatically logs email sending activity:
// Audit logs are created automatically
// View them from the audit_logs table
const { data : logs } = await supabase
. from ( 'audit_logs' )
. select ( '*' )
. eq ( 'action' , 'sent_confirmation_emails' )
. eq ( 'entity_type' , 'event' )
. eq ( 'entity_id' , eventId )
. order ( 'created_at' , { ascending: false })
if ( logs ) {
logs . forEach ( log => {
const details = log . details as any
console . log ( ` ${ log . created_at } : Sent ${ details . sent } , Failed ${ details . failed } ` )
})
}
Email Layout
Emails are automatically wrapped in a responsive layout with your organization branding:
<! 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;" >
<!-- Your email content here -->
</ div >
< div style = "text-align:center; padding:16px; color:#71717a; font-size:12px;" >
< p > Your Organization — Powered by PassTru </ p >
</ div >
</ div >
</ body >
</ html >
QR Code Generation
QR codes are generated using the QR Server API:
const uniqueId = 'K7M9P2Q5'
const qrCodeUrl = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data= ${ encodeURIComponent ( uniqueId ) } `
// This URL is automatically included in emails as {{qr_code_url}}
Error Handling
Common email sending errors:
Unauthorized : User doesn’t have access to the event
Invalid attendee IDs : UUIDs are malformed
No attendees found : Attendee IDs don’t exist
Resend API error : Service unavailable or rate limited
const { data , error } = await supabase . functions . invoke ( 'send-confirmation-email' , {
body: { attendee_ids: attendeeIds }
})
if ( error ) {
console . error ( 'Email sending error:' , error . message )
// Check for specific errors
if ( error . message . includes ( 'Unauthorized' )) {
console . error ( 'You do not have permission to send emails for this event' )
} else if ( error . message . includes ( 'Invalid attendee_ids' )) {
console . error ( 'One or more attendee IDs are invalid' )
} else {
console . error ( 'An unexpected error occurred' )
}
} else {
// Check individual results
data . results . forEach ( result => {
if ( ! result . success ) {
console . error ( `Failed to send email to attendee ${ result . id } : ${ result . error } ` )
}
})
}
Rate Limits
Resend API limits (free tier):
100 emails per day
3,000 emails per month
Upgrade to paid plans for higher limits
To avoid rate limits, send emails in batches with delays between batches. See the “Bulk Email Sending” example above.
Security
The email function includes security measures:
Authentication required : User must be signed in
Authorization checks : User must own or be assigned to the event
Input validation : Attendee IDs and email addresses are validated
HTML escaping : User data is escaped to prevent injection
Test mode : Use test_email_override to avoid sending to real users during testing
Next Steps
Attendees API Manage attendees and their data
Check-ins API Handle check-ins at your event