Aggiungi notifiche SMS al tuo sito di prenotazione con IA
Invia un SMS automatico quando qualcuno invia una richiesta di prenotazione.
Roadmap e risorse
Nuovo qui? Parti prima da qui
Questa guida aggiorna il progetto del sito di prenotazione con IA aggiungendo notifiche SMS automatiche. Se non hai ancora creato il sito di prenotazione, parti prima dalla guida principale.
Crea un sito di prenotazione con l'IA
Apri la guida principale
MAIN GUIDE
https://profitstudio.app/video/build-booking-website-ai
Recupera le tue informazioni Twilio
Crea un account Twilio
Apri Twilio
https://www.twilio.com/
Recupera questi valori dalla tua dashboard Twilio:
Valori Twilio
TWILIO_ACCOUNT_SID TWILIO_AUTH_TOKEN TWILIO_PHONE_NUMBER
TWILIO_ACCOUNT_SID
Account SID
TWILIO_AUTH_TOKEN
Auth Token
TWILIO_PHONE_NUMBER
Numero di telefono Twilio
Scegli un numero Twilio che supporti gli SMS
Aggiungi i secret in Supabase
Apri i secret delle Edge Functions in Supabase
Apri Supabase
https://supabase.com/dashboard/projects
Supabase → Project Settings → Edge Functions → Secrets
Aggiungi i secret Twilio richiesti
Secret richiesti
TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx TWILIO_AUTH_TOKEN=your_auth_token TWILIO_PHONE_NUMBER=+1234567890
L'SMS al cliente è abilitato di default. L'SMS al proprietario è opzionale ed è disabilitato di default.
Opzionale: invia un SMS al titolare del business
SMS al proprietario (opzionale)
SEND_OWNER_SMS=true OWNER_PHONE_NUMBER=+1234567890
Aggiungili solo se vuoi che anche il titolare del business riceva un SMS quando viene inviata una nuova richiesta di prenotazione.
Crea l'Edge Function SMS
Crea una nuova Edge Function con esattamente questo nome: send-sms-notification
Controlla il modello del messaggio SMS
Copia e incolla il codice della funzione SMS generata
Funzione di notifica 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, }); });
Nome del business
Il nome della tua attività
Messaggio SMS al cliente
Ciao {{customer_name}}, abbiamo ricevuto la tua richiesta di prenotazione per {{service_name}} il {{appointment_date}} alle {{appointment_time}}. Ti contatteremo presto per confermare.
Messaggio SMS al proprietario
Nuova richiesta di prenotazione: {{customer_name}} ha richiesto {{service_name}} il {{appointment_date}} alle {{appointment_time}}. Telefono: {{customer_phone}}
Opzionale: aggiorna i nomi dei campi se necessario
Questa funzione SMS funziona con i campi di prenotazione comuni come: full_name, phone, appointment_date, start_time, service_id e service_name. Se il tuo progetto usa nomi di campo diversi, aggiorna FIELD_MAP all'interno della funzione per farlo corrispondere alla tua tabella di prenotazione.
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"], };
Collega il Webhook
Scegli la tua tabella delle prenotazioni
In questo video usiamo: public.appointments Se il tuo progetto usa una tabella di prenotazione diversa, scegli quella tabella.
Vai ai Webhooks del database Supabase
Supabase → Database → Webhooks → Create webhook
Usa queste impostazioni del webhook
Impostazioni del webhook
Table: public.appointments Event: Insert Type: Supabase Edge Functions Function: send-sms-notification Method: POST
Attiva solo l'evento Insert
Testa la notifica SMS
Crea una nuova prenotazione dal tuo sito
Verifica se il cliente ha ricevuto l'SMS
Controlla i log della Edge Function in Supabase
Supabase → Edge Functions → send-sms-notification → Logs
Se necessario, controlla i log Messaging di Twilio
Twilio → Messaging → Logs
Prima di andare in produzione, controlla le regole SMS del tuo paese
I requisiti SMS variano da paese a paese
I requisiti SMS possono variare in base al paese di destinazione. Prima di usarlo in produzione, controlla i requisiti di messaggistica di Twilio per il tuo paese di destinazione. Ad esempio, gli SMS inviati da app verso numeri statunitensi potrebbero richiedere la registrazione A2P 10DLC.