Adicione notificações SMS ao seu site de agendamento com IA

Envie um SMS automático quando alguém envia uma solicitação de agendamento.

Roadmap e recursos

É novo por aqui? Comece por aqui primeiro

Este guia melhora o projeto do site de agendamento com IA com notificações SMS automáticas. Se ainda não criou o site de agendamento, comece pelo guia principal primeiro.

Crie um site de agendamento com IA

Abra o guia principal

MAIN GUIDE

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

Obtenha as suas credenciais Twilio

Crie uma conta Twilio

Abrir o Twilio

https://www.twilio.com/

Obtenha estes valores no seu painel da Twilio:

Valores da Twilio

TWILIO_ACCOUNT_SID TWILIO_AUTH_TOKEN TWILIO_PHONE_NUMBER

TWILIO_ACCOUNT_SID

Account SID

TWILIO_AUTH_TOKEN

Auth Token

TWILIO_PHONE_NUMBER

Número de telefone da Twilio

Escolha um número Twilio que suporte SMS

Adicione os segredos no Supabase

Abrir os segredos das Edge Functions no Supabase

Abrir o Supabase

https://supabase.com/dashboard/projects

Supabase → Project Settings → Edge Functions → Secrets

Adicione os segredos Twilio necessários

Segredos necessários

TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx TWILIO_AUTH_TOKEN=your_auth_token TWILIO_PHONE_NUMBER=+1234567890

O SMS para o cliente está ativado por padrão. O SMS para o dono é opcional e está desativado por padrão.

Opcional: enviar SMS para o dono do negócio

SMS para o dono (opcional)

SEND_OWNER_SMS=true OWNER_PHONE_NUMBER=+1234567890

Adicione-os apenas se você também quiser que o dono do negócio receba um SMS quando uma nova solicitação de agendamento for enviada.

Crie a Edge Function de SMS

Crie uma nova Edge Function com este nome exato: send-sms-notification

Revise o modelo da mensagem SMS

Copie e cole o código da função SMS gerada

Função de notificação 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 do negócio

O nome do seu negócio

Mensagem SMS para o cliente

Olá {{customer_name}}, recebemos a sua solicitação de agendamento de {{service_name}} para {{appointment_date}} às {{appointment_time}}. Entraremos em contato em breve para confirmar.

Mensagem SMS para o dono

Nova solicitação de agendamento: {{customer_name}} solicitou {{service_name}} em {{appointment_date}} às {{appointment_time}}. Telefone: {{customer_phone}}

Opcional: atualize os nomes dos campos se necessário

Esta função SMS funciona com campos de agendamento comuns como: full_name, phone, appointment_date, start_time, service_id e service_name. Se o seu projeto usar nomes de campo diferentes, atualize FIELD_MAP dentro da função para corresponder à sua tabela de agendamentos.

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"], };

Conecte o Webhook

Escolha a sua tabela de agendamentos

Neste vídeo usamos: public.appointments Se o seu projeto usar uma tabela de agendamentos diferente, escolha essa tabela.

Vá para os Webhooks da base de dados do Supabase

Supabase → Database → Webhooks → Create webhook

Use estas configurações de webhook

Configurações do webhook

Table: public.appointments Event: Insert Type: Supabase Edge Functions Function: send-sms-notification Method: POST

Ative apenas o evento Insert

Teste a notificação SMS

Crie um novo agendamento a partir do seu site

Verifique se o cliente recebeu o SMS

Verifique os logs da Edge Function no Supabase

Supabase → Edge Functions → send-sms-notification → Logs

Verifique os logs de Messaging na Twilio se necessário

Twilio → Messaging → Logs

Antes de ir para produção, verifique as regras de SMS do seu país

Os requisitos de SMS variam conforme o país

Os requisitos de SMS podem variar conforme o país de destino. Antes de usar isto em produção, verifique os requisitos de mensagens da Twilio para o seu país de destino. Por exemplo, SMS enviados por aplicativos para números dos EUA podem exigir o registro A2P 10DLC.