Añade notificaciones por email a tu sitio de reservas con IA

Envía emails de confirmación de reserva y alertas al propietario usando Supabase, Resend y Edge Functions.

Hoja de ruta y recursos

¿Nuevo? Empieza aquí primero

Esta guía amplía el proyecto del sitio de reservas con IA con notificaciones por email automáticas. Si todavía no has construido el sitio de reservas, empieza primero con la guía principal.

Construye un sitio de reservas con IA

Abre la guía principal

MAIN GUIDE

https://profitstudio.app/video/build-booking-website-ai

Configurar Resend para los emails de reserva

Crea y configura tu cuenta de Resend

Abrir Resend

https://resend.com/

Crea una cuenta de Resend Genera una clave API de Resend Copia y guarda la clave API de forma segura

IMPORTANTE

Mantén privada la clave API de Resend. Nunca la añadas al código del frontend ni la compartas públicamente.

Añade y verifica tu dominio de envío

Añade el dominio de tu sitio de reservas Selecciona la región más cercana Copia los registros DNS de Resend Añade los registros en tu proveedor de dominio Vuelve a Resend y confirma que el dominio está verificado

La verificación DNS puede tardar unos minutos o más, según tu proveedor de dominio.

Crear la función de email de reservas

Crea la Edge Function de correo de reservas

Abrir Supabase

https://supabase.com/?utm_source=partner&utm_medium=social&utm_campaign=supasquad&dub_id=faVfKdcLWvKCbYNp

Abre tu proyecto de Supabase Abre Edge Functions Crea una nueva función con el editor del navegador Nombra la función `send-booking-email` Elimina el código de ejemplo predeterminado

Personaliza y añade el código de la función

Introduce el correo del propietario Introduce el dominio verificado de Resend Introduce el nombre del negocio remitente Personaliza las plantillas de correo si es necesario Copia y pega el código de función generado

Función de email de reservas

import { serve } from "https://deno.land/std/http/server.ts"; serve(async (req) => { try { const body = await req.json(); const record = body.record || {}; const resendKey = Deno.env.get("RESEND_API_KEY"); const supabaseUrl = Deno.env.get("SUPABASE_URL"); const supabaseServiceRoleKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY"); if (!resendKey) { return new Response( JSON.stringify({ error: "Missing RESEND_API_KEY secret" }), { status: 500, headers: { "Content-Type": "application/json" }, } ); } const ownerEmail = "YOUR_OWNER_EMAIL"; const customerEmail = record.email || ""; const customerName = record.full_name || record.name || "Customer"; const phone = record.phone || ""; const appointmentDate = record.appointment_date || record.date || ""; const startTime = record.start_time || record.time || ""; const endTime = record.end_time || ""; const notes = record.notes || ""; const status = record.status || "New"; let serviceName = record.service_name || record.service || record.service_title || ""; // If the appointment only has service_id, fetch the service name from the services table if (!serviceName && record.service_id && supabaseUrl && supabaseServiceRoleKey) { try { const serviceId = encodeURIComponent(record.service_id); const serviceResponse = await fetch( `${supabaseUrl}/rest/v1/services?id=eq.${serviceId}&select=name`, { method: "GET", headers: { apikey: supabaseServiceRoleKey, Authorization: `Bearer ${supabaseServiceRoleKey}`, "Content-Type": "application/json", }, } ); const serviceData = await serviceResponse.json(); if (Array.isArray(serviceData) && serviceData.length > 0) { serviceName = serviceData[0].name || ""; } } catch (_serviceError) { serviceName = ""; } } if (!serviceName) { serviceName = "Appointment"; } // 1) Email to business owner const ownerResponse = await fetch("https://api.resend.com/emails", { method: "POST", headers: { Authorization: `Bearer ${resendKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ from: "Bookings <bookings@YOUR_DOMAIN_NAME>", to: [ownerEmail], subject: "OWNER_NOTIFICATION_SUBJECT", html: ` <h2>OWNER_NOTIFICATION_HEADING</h2> <p>OWNER_NOTIFICATION_INTRO</p> <p><b>Customer Name:</b> ${customerName}</p> <p><b>Email:</b> ${customerEmail}</p> <p><b>Phone:</b> ${phone}</p> <p><b>Service:</b> ${serviceName}</p> <p><b>Date:</b> ${appointmentDate}</p> <p><b>Start Time:</b> ${startTime}</p> <p><b>End Time:</b> ${endTime}</p> <p><b>Notes:</b> ${notes}</p> <p><b>Status:</b> ${status}</p> `, }), }); const ownerData = await ownerResponse.json(); // 2) Email to customer let customerData = null; if (customerEmail) { const customerResponse = await fetch("https://api.resend.com/emails", { method: "POST", headers: { Authorization: `Bearer ${resendKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ from: "YOUR_BUSINESS_NAME <bookings@YOUR_DOMAIN_NAME>", to: [customerEmail], subject: "CUSTOMER_CONFIRMATION_SUBJECT", html: ` <h2>CUSTOMER_CONFIRMATION_HEADING</h2> <p>Hi ${customerName},</p> <p>CUSTOMER_CONFIRMATION_INTRO</p> <p><b>Service:</b> ${serviceName}</p> <p><b>Date:</b> ${appointmentDate}</p> <p><b>Start Time:</b> ${startTime}</p> <p><b>End Time:</b> ${endTime}</p> <p>If you need to make any changes, please contact us before your appointment.</p> <p>CUSTOMER_CONFIRMATION_CLOSING</p> `, }), }); customerData = await customerResponse.json(); } return new Response( JSON.stringify({ success: true, ownerEmailSent: ownerData, customerEmailSent: customerData, }), { headers: { "Content-Type": "application/json" }, } ); } catch (error) { return new Response( JSON.stringify({ success: false, error: String(error), }), { status: 500, headers: { "Content-Type": "application/json" }, } ); } });

Email del propietario

owner@example.com

Dominio verificado en Resend

yourdomain.com

Nombre del negocio remitente

Clínica Dental Bright

Asunto del email al propietario

Encabezado del email al propietario

Texto introductorio del email al propietario

Asunto del email al cliente

Encabezado del email al cliente

Texto introductorio del email al cliente

Texto de cierre del email al cliente

Despliega y configura la función

Despliega la función Abre la configuración de la función Desactiva `Verify JWT With Legacy Secret` Guarda los cambios Copia y guarda la URL del endpoint de la función para configurar el webhook

IMPORTANTE

Desactiva “Verify JWT With Legacy Secret” para que el Database Webhook pueda llamar a la función.

Añadir la API key de Resend a Supabase

Añade la clave API de Resend como secreto en Supabase

Abre Edge Function Secrets Crea un nuevo secreto Establece el nombre en `RESEND_API_KEY` Pega la clave API de Resend Guarda el secreto

IMPORTANTE

Usa exactamente el nombre de secreto `RESEND_API_KEY`. Cualquier error tipográfico impedirá que la función acceda a Resend.

Conectar los emails a las nuevas reservas

Crea el webhook de correo de reservas

Activa la función de correo de reserva cada vez que se añada una nueva cita.

Integrations Database Webhooks

Abre Database Webhooks Activa Database Webhooks Crea un nuevo webhook Nómbralo `send-booking-email-webhook` Selecciona la tabla `public.appointments` Elige `Insert` como evento Mantén `HTTP Request` como tipo Usa `POST` como método de solicitud Pega la URL del endpoint de la Edge Function Crea el webhook

Probar el sistema de email de reservas

Prueba el flujo completo de correos de reserva

Prueba el flujo completo de reserva y confirma que ambos correos se entregan correctamente.

Abre el sitio de reservas en vivo Envía una cita de prueba Confirma que la reserva se guarda en Supabase Revisa el correo de confirmación del cliente Revisa el correo de notificación del propietario Verifica que los datos de la reserva sean correctos en ambos correos Confirma que la reserva aparece en el panel de administración