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.