Ajoute des notifications SMS à ton site de réservation IA
Envoie un SMS automatique lorsqu'une personne envoie une demande de réservation.
Feuille de route et ressources
Tu débutes ? Commence ici en premier
Ce guide améliore le projet de site de réservation IA avec des notifications SMS automatiques. Si tu n'as pas encore construit le site de réservation, commence d'abord par le guide principal.
Crée un site de réservation avec l'IA
Ouvre le guide principal
MAIN GUIDE
https://profitstudio.app/video/build-booking-website-ai
Obtiens tes informations Twilio
Crée un compte Twilio
Ouvrir Twilio
https://www.twilio.com/
Récupère ces valeurs depuis ton tableau de bord Twilio :
Valeurs Twilio
TWILIO_ACCOUNT_SID TWILIO_AUTH_TOKEN TWILIO_PHONE_NUMBER
TWILIO_ACCOUNT_SID
Account SID
TWILIO_AUTH_TOKEN
Auth Token
TWILIO_PHONE_NUMBER
Numéro de téléphone Twilio
Choisis un numéro Twilio qui prend en charge les SMS
Ajoute les secrets dans Supabase
Ouvrir les secrets Edge Function de Supabase
Ouvrir Supabase
https://supabase.com/dashboard/projects
Supabase → Project Settings → Edge Functions → Secrets
Ajoute les secrets Twilio requis
Secrets requis
TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx TWILIO_AUTH_TOKEN=your_auth_token TWILIO_PHONE_NUMBER=+1234567890
Le SMS au client est activé par défaut. Le SMS au propriétaire est optionnel et désactivé par défaut.
Optionnel : Envoyer un SMS au propriétaire du business
SMS propriétaire optionnel
SEND_OWNER_SMS=true OWNER_PHONE_NUMBER=+1234567890
Ajoute-les uniquement si tu veux aussi que le propriétaire du business reçoive un SMS lorsqu'une nouvelle demande de réservation est envoyée.
Crée l'Edge Function SMS
Crée une nouvelle Edge Function avec exactement ce nom : send-sms-notification
Vérifie le modèle de message SMS
Copie et colle le code de la fonction SMS générée
Fonction de notification SMS
// supabase/functions/send-sms-notification/index.ts // // Sends SMS notifications when a new booking request is created. // Default: sends booking request SMS to the customer. // Optional: also sends SMS notification to the business owner. // // Required Supabase Secrets: // TWILIO_ACCOUNT_SID // TWILIO_AUTH_TOKEN // TWILIO_PHONE_NUMBER // // Optional Owner SMS Secrets: // SEND_OWNER_SMS=true // OWNER_PHONE_NUMBER=+1234567890 const CORS_HEADERS = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "POST, OPTIONS", "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type", }; // ===== CUSTOMIZE THESE VALUES ===== const BUSINESS_NAME = "Your Business Name"; const CUSTOMER_SMS_TEMPLATE = "Hi {{customer_name}}, we received your {{service_name}} booking request for {{appointment_date}} at {{appointment_time}}. We'll contact you soon to confirm."; const OWNER_SMS_TEMPLATE = "New booking request: {{customer_name}} requested {{service_name}} on {{appointment_date}} at {{appointment_time}}. Phone: {{customer_phone}}"; // Default booking field names used in this guide. // If your project uses different field names, update them here. const FIELD_MAP = { customerName: ["full_name", "customer_name", "name"], customerPhone: ["phone", "phone_number", "customer_phone"], appointmentDate: ["appointment_date", "date", "booking_date"], appointmentTime: ["start_time", "appointment_time", "time"], serviceName: ["service_name", "service"], serviceId: ["service_id"], }; // ===== END CUSTOMIZATION ===== function json(body: unknown, status = 200): Response { return new Response(JSON.stringify(body), { status, headers: { ...CORS_HEADERS, "Content-Type": "application/json" }, }); } function getEnvFlag(name: string, defaultValue = false): boolean { const value = Deno.env.get(name); if (!value) return defaultValue; return value.toLowerCase() === "true"; } function getField(record: Record<string, unknown>, keys: string[]): string { for (const key of keys) { const value = record[key]; if (typeof value === "string" && value.trim()) { return value.trim(); } } return ""; } function formatTime(time: string): string { if (!time) return ""; return time.length >= 5 ? time.slice(0, 5) : time; } function renderTemplate( template: string, values: Record<string, string>, ): string { return template.replace(/\{\{(.*?)\}\}/g, (_, key) => { return values[key.trim()] ?? ""; }); } async function fetchServiceName( serviceId: string, supabaseUrl: string, serviceRoleKey: string, ): Promise<string> { try { const url = `${supabaseUrl}/rest/v1/services?id=eq.${encodeURIComponent( serviceId, )}&select=name`; const response = await fetch(url, { headers: { apikey: serviceRoleKey, Authorization: `Bearer ${serviceRoleKey}`, }, }); if (!response.ok) return ""; const rows = await response.json(); return rows?.[0]?.name ?? ""; } catch { return ""; } } async function sendTwilioSms(options: { accountSid: string; authToken: string; from: string; to: string; body: string; }) { const { accountSid, authToken, from, to, body } = options; const url = `https://api.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json`; const form = new URLSearchParams({ From: from, To: to, Body: body, }); const response = await fetch(url, { method: "POST", headers: { Authorization: "Basic " + btoa(`${accountSid}:${authToken}`), "Content-Type": "application/x-www-form-urlencoded", }, body: form.toString(), }); const data = await response.json().catch(() => null); return { ok: response.ok, status: response.status, data, }; } Deno.serve(async (req) => { if (req.method === "OPTIONS") { return new Response("ok", { headers: CORS_HEADERS }); } if (req.method !== "POST") { return json({ success: false, error: "Method not allowed" }, 405); } const accountSid = Deno.env.get("TWILIO_ACCOUNT_SID"); const authToken = Deno.env.get("TWILIO_AUTH_TOKEN"); const twilioPhoneNumber = Deno.env.get("TWILIO_PHONE_NUMBER"); const sendCustomerSms = getEnvFlag("SEND_CUSTOMER_SMS", true); const sendOwnerSms = getEnvFlag("SEND_OWNER_SMS", false); const ownerPhoneNumber = Deno.env.get("OWNER_PHONE_NUMBER"); if (!accountSid || !authToken || !twilioPhoneNumber) { return json( { success: false, error: "Missing Twilio secrets. Please add TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, and TWILIO_PHONE_NUMBER.", }, 500, ); } let payload; try { payload = await req.json(); } catch { return json({ success: false, error: "Invalid JSON body" }, 400); } const record = payload?.record; if (!record) { return json({ success: false, error: "Missing record in payload" }, 400); } const customerName = getField(record, FIELD_MAP.customerName) || "Customer"; const customerPhone = getField(record, FIELD_MAP.customerPhone); const appointmentDate = getField(record, FIELD_MAP.appointmentDate) || "your selected date"; const appointmentTime = formatTime(getField(record, FIELD_MAP.appointmentTime)) || "your selected time"; let serviceName = getField(record, FIELD_MAP.serviceName) || "your service"; const serviceId = getField(record, FIELD_MAP.serviceId); const supabaseUrl = Deno.env.get("SUPABASE_URL"); const serviceRoleKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY"); if (serviceId && supabaseUrl && serviceRoleKey) { const fetchedServiceName = await fetchServiceName( serviceId, supabaseUrl, serviceRoleKey, ); if (fetchedServiceName) { serviceName = fetchedServiceName; } } const templateValues = { business_name: BUSINESS_NAME, customer_name: customerName, customer_phone: customerPhone || "Not provided", service_name: serviceName, appointment_date: appointmentDate, appointment_time: appointmentTime, }; const results = { customer_sms: "not_sent", owner_sms: "not_sent", errors: [] as unknown[], }; if (sendCustomerSms) { if (!customerPhone) { results.customer_sms = "skipped_missing_customer_phone"; } else { const customerMessage = renderTemplate( CUSTOMER_SMS_TEMPLATE, templateValues, ); const customerResult = await sendTwilioSms({ accountSid, authToken, from: twilioPhoneNumber, to: customerPhone, body: customerMessage, }); if (!customerResult.ok) { results.customer_sms = "failed"; results.errors.push({ type: "customer_sms_failed", status: customerResult.status, twilio: customerResult.data, }); } else { results.customer_sms = "sent"; } } } if (sendOwnerSms) { if (!ownerPhoneNumber) { results.owner_sms = "skipped_missing_owner_phone"; } else { const ownerMessage = renderTemplate( OWNER_SMS_TEMPLATE, templateValues, ); const ownerResult = await sendTwilioSms({ accountSid, authToken, from: twilioPhoneNumber, to: ownerPhoneNumber, body: ownerMessage, }); if (!ownerResult.ok) { results.owner_sms = "failed"; results.errors.push({ type: "owner_sms_failed", status: ownerResult.status, twilio: ownerResult.data, }); } else { results.owner_sms = "sent"; } } } if (results.errors.length > 0) { return json( { success: false, message: "One or more SMS messages failed.", results, }, 502, ); } return json({ success: true, message: "SMS notification process completed.", results, }); });
Nom du business
Le nom de ton business
Message SMS au client
Bonjour {{customer_name}}, nous avons bien reçu ta demande de réservation {{service_name}} pour le {{appointment_date}} à {{appointment_time}}. Nous te contacterons bientôt pour la confirmer.
Message SMS au propriétaire
Nouvelle demande de réservation : {{customer_name}} a demandé {{service_name}} le {{appointment_date}} à {{appointment_time}}. Téléphone : {{customer_phone}}
Optionnel : mets à jour les noms de champs si nécessaire
Cette fonction SMS fonctionne avec les champs de réservation courants comme : full_name, phone, appointment_date, start_time, service_id et service_name. Si ton projet utilise des noms de champs différents, mets à jour FIELD_MAP dans la fonction pour correspondre à ta table de réservation.
FIELD_MAP
const FIELD_MAP = { customerName: ["full_name", "customer_name", "name"], customerPhone: ["phone", "phone_number", "customer_phone"], appointmentDate: ["appointment_date", "date", "booking_date"], appointmentTime: ["start_time", "appointment_time", "time"], serviceName: ["service_name", "service"], serviceId: ["service_id"], };
Connecte le Webhook
Choisis ta table de réservations
Dans cette vidéo, nous utilisons : public.appointments Si ton projet utilise une autre table de réservation, choisis cette table à la place.
Va dans les Webhooks de la base de données Supabase
Supabase → Database → Webhooks → Create webhook
Utilise ces paramètres de webhook
Paramètres du webhook
Table: public.appointments Event: Insert Type: Supabase Edge Functions Function: send-sms-notification Method: POST
Active uniquement l'événement Insert
Teste la notification SMS
Crée une nouvelle réservation depuis ton site
Vérifie si le client a reçu le SMS
Consulte les logs Edge Function de Supabase
Supabase → Edge Functions → send-sms-notification → Logs
Consulte les logs Messaging de Twilio si nécessaire
Twilio → Messaging → Logs
Avant la mise en production, vérifie les règles SMS de ton pays
Les exigences SMS varient selon le pays
Les exigences SMS peuvent varier selon le pays de destination. Avant d'utiliser ceci en production, vérifie les exigences de messagerie de Twilio pour ton pays cible. Par exemple, les SMS applicatifs vers des numéros américains peuvent nécessiter un enregistrement A2P 10DLC.