Füge SMS-Benachrichtigungen zu deiner KI-Buchungs-Website hinzu
Sende automatisch eine SMS, wenn jemand eine Buchungsanfrage absendet.
Roadmap & Ressourcen
Neu hier? Starte zuerst hier
Diese Anleitung erweitert das KI-Buchungs-Website-Projekt um automatische SMS-Benachrichtigungen. Wenn du die Buchungs-Website noch nicht erstellt hast, beginne zuerst mit der Hauptanleitung.
Baue eine Buchungs-Website mit KI
Öffne die Hauptanleitung
MAIN GUIDE
https://profitstudio.app/video/build-booking-website-ai
Hole deine Twilio-Daten
Erstelle ein Twilio-Konto
Öffne Twilio
https://www.twilio.com/
Hole diese Werte aus deinem Twilio-Dashboard:
Twilio-Werte
TWILIO_ACCOUNT_SID TWILIO_AUTH_TOKEN TWILIO_PHONE_NUMBER
TWILIO_ACCOUNT_SID
Account SID
TWILIO_AUTH_TOKEN
Auth Token
TWILIO_PHONE_NUMBER
Twilio-Telefonnummer
Wähle eine Twilio-Nummer, die SMS unterstützt
Füge die Secrets in Supabase hinzu
Öffne die Edge-Function-Secrets in Supabase
Öffne Supabase
https://supabase.com/dashboard/projects
Supabase → Project Settings → Edge Functions → Secrets
Füge die erforderlichen Twilio-Secrets hinzu
Erforderliche Secrets
TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx TWILIO_AUTH_TOKEN=your_auth_token TWILIO_PHONE_NUMBER=+1234567890
Die Kunden-SMS ist standardmäßig aktiviert. Die Inhaber-SMS ist optional und standardmäßig deaktiviert.
Optional: SMS an den Geschäftsinhaber senden
Optionale Inhaber-SMS
SEND_OWNER_SMS=true OWNER_PHONE_NUMBER=+1234567890
Füge diese nur hinzu, wenn auch der Geschäftsinhaber eine SMS erhalten soll, sobald eine neue Buchungsanfrage gesendet wird.
Erstelle die SMS-Edge-Function
Erstelle eine neue Edge Function mit genau diesem Namen: send-sms-notification
Überprüfe die SMS-Nachrichtenvorlage
Kopiere und füge den generierten SMS-Funktionscode ein
SMS-Benachrichtigungsfunktion
// 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, }); });
Geschäftsname
Dein Unternehmensname
Kunden-SMS-Nachricht
Hallo {{customer_name}}, wir haben deine Buchungsanfrage für {{service_name}} am {{appointment_date}} um {{appointment_time}} erhalten. Wir melden uns in Kürze bei dir, um sie zu bestätigen.
Inhaber-SMS-Nachricht
Neue Buchungsanfrage: {{customer_name}} hat {{service_name}} am {{appointment_date}} um {{appointment_time}} angefragt. Telefon: {{customer_phone}}
Optional: Aktualisiere die Feldnamen bei Bedarf
Diese SMS-Funktion arbeitet mit gängigen Buchungsfeldern wie: full_name, phone, appointment_date, start_time, service_id und service_name. Wenn dein Projekt andere Feldnamen verwendet, aktualisiere FIELD_MAP innerhalb der Funktion so, dass es zu deiner Buchungstabelle passt.
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"], };
Verbinde den Webhook
Wähle deine Buchungstabelle
In diesem Video verwenden wir: public.appointments Wenn dein Projekt eine andere Buchungstabelle verwendet, wähle diese Tabelle stattdessen.
Gehe zu den Database-Webhooks in Supabase
Supabase → Database → Webhooks → Create webhook
Verwende diese Webhook-Einstellungen
Webhook-Einstellungen
Table: public.appointments Event: Insert Type: Supabase Edge Functions Function: send-sms-notification Method: POST
Aktiviere nur das Insert-Ereignis
Teste die SMS-Benachrichtigung
Erstelle eine neue Buchung über deine Website
Prüfe, ob der Kunde die SMS erhalten hat
Prüfe die Edge-Function-Logs in Supabase
Supabase → Edge Functions → send-sms-notification → Logs
Prüfe bei Bedarf die Messaging-Logs in Twilio
Twilio → Messaging → Logs
Bevor du live gehst, prüfe die SMS-Regeln deines Landes
SMS-Anforderungen unterscheiden sich je nach Land
Die SMS-Anforderungen können je nach Zielland unterschiedlich sein. Bevor du dies im Produktivbetrieb einsetzt, prüfe die Messaging-Anforderungen von Twilio für dein Zielland. Zum Beispiel kann für app-basierte SMS an US-Nummern eine A2P-10DLC-Registrierung erforderlich sein.