Without a .ics attachment, the booking lives only inside your email. Customers have to manually re-type the date, time, and timezone into their calendar — and half of them won't. The no-show rate for booking flows without calendar attachments is measurably higher than flows that attach .ics at confirmation time, because a calendar entry is the only thing that triggers the phone reminder ten minutes before the appointment. A text-only confirmation email is a user-experience failure that directly converts into lost revenue.
Medium because the gap increases no-show rate and rebooking cost, but the booking itself still exists server-side.
Generate the .ics synchronously inside the confirmation handler and attach it to the same email — do not ship a follow-up email with the invite, because customers open the confirmation once and never return. Set the attachment content-type to text/calendar. Edit src/lib/email.ts:
const icsData = await generateBookingICS(booking, booking.host)
await resend.emails.send({
from: FROM_EMAIL,
to: booking.customer_email,
subject: `Confirmed: ${booking.service_name}`,
html: confirmationEmailTemplate(booking),
attachments: [{
filename: 'booking.ics',
content: Buffer.from(icsData).toString('base64'),
contentType: 'text/calendar',
}],
})
ID: booking-notifications-reminders.calendar-invites.ics-attached
Severity: medium
What to look for: Find the confirmation email sending code (the same function checked in confirmation-sent-60s). Search for: (1) An attachment array or attachment parameter that includes a .ics file with Content-Type text/calendar. (2) Alternatively, a direct download link to the .ics file in the email body. Verify the .ics generation happens BEFORE the email is sent (not asynchronously after). Check the email provider's attachment API to confirm the .ics is properly formatted.
Pass criteria: Count all attachment methods found (attachment or download link). Confirmation email includes .ics as an attachment (Content-Type: text/calendar) OR a direct download link to a .ics endpoint — at least 1 delivery method must exist. Quote the attachment configuration or the link in the email template. The .ics must be generated before the email is dispatched, not lazily. A confirmation email with no attachment and no download link does NOT count as pass.
Fail criteria: Confirmation email sent without .ics attachment or link. .ics link is broken or returns 404. .ics generated asynchronously after the email (customer gets email without calendar invite, then a separate email with invite later). Attachment Content-Type is wrong (not text/calendar).
Skip (N/A) when: Calendar invites not implemented (same criteria as ics-complete-uid skip).
Detail on fail: Quote the confirmation email sending code and state whether an attachment is present. Example: "sendConfirmationEmail() at src/lib/email.ts:45 sends email via resend.emails.send() with no attachments parameter. No .ics download link found in the email template at src/emails/confirmation.tsx.".
Remediation: Attach .ics to the confirmation email before sending:
// src/lib/email.ts
async function sendConfirmationEmail(booking: Booking) {
const icsData = await generateBookingICS(booking, booking.host)
await resend.emails.send({
from: FROM_EMAIL,
to: booking.customer_email,
subject: `Booking Confirmed: ${booking.service_name}`,
html: confirmationEmailTemplate(booking),
attachments: [
{
filename: 'booking.ics',
content: Buffer.from(icsData).toString('base64'),
contentType: 'text/calendar',
}
]
})
}
Cross-reference: Email/SMS Compliance audit checks email attachment compliance. Booking Flow & Lifecycle audit checks confirmation flow completeness. Accessibility Fundamentals audit checks that the .ics download link is keyboard-accessible.