SMS Settings Email Function Supabase Telnyx
Deploy SMS Edge Function (Telnyx)
One-time setup — once deployed, your flow's Send SMS nodes will fire real text messages through Telnyx's messaging API. No browser limitations, no secrets exposed — everything runs server-side inside Supabase Edge Functions.
Telnyx API v2 API key stays server-side No CLI needed E.164 phone format
Dashboard (Browser) RECOMMENDED
CLI

Step 0 — Get Your Telnyx Credentials

Create a free Telnyx account at telnyx.com/sign-up. New accounts get $10 free credit — enough to send ~300 SMS messages for testing.
Buy a phone number — in the Telnyx Mission Control Portal go to Numbers → Search & Buy Numbers. Filter for SMS-capable numbers. US long-code numbers start at ~$1/month. The number becomes your SMS sender (e.g. +12025551234).
Create an API Key v2 — go to portal.telnyx.com → API Keys → click Create API Key. Give it a name like Agent Builder. Copy the key — it starts with KEY0…. Store it securely — you won't see it again.
(Optional) Create a Messaging Profile — in Messaging → Messaging Profiles, create a profile and assign your phone number to it. Copy the Profile ID (UUID). This is optional but recommended for production — it groups your numbers and enables delivery webhooks.
Enter credentials in SMS Settings — open SMS Settings, paste your API Key, From Number, and (optional) Messaging Profile ID. Come back here to deploy the Edge Function.

Deploy the Edge Function

1
Open Edge Functions in Supabase Dashboard
No install needed — runs entirely in your browser
Open Supabase → Edge Functions
Sign in → select your project → click Edge Functions in the left sidebar.
Click the New Function button (top-right area).
2
Name it exactly send-sms
The app calls this exact URL path — spelling matters
1
In the Function name field type exactly: send-sms
2
Set Verify JWTOFF. The app passes Authorization: Bearer <anon_key> in the header — JWT verification isn't needed.
3
Click Create function — the inline code editor opens.
3
Paste the function code
Copy the complete block below and replace everything in the editor
// ================================================================
//  send-sms — Supabase Edge Function
//  Sends SMS via Telnyx API v2.
//  https://developers.telnyx.com/docs/messaging/messages/send-message
//
//  Deploy via Supabase Dashboard:
//    Edge Functions → New Function "send-sms" → JWT OFF → paste → Deploy
//
//  POST /functions/v1/send-sms
//  Headers: { Authorization: Bearer <anon_key> }
//  Body:
//    Option A (inline test):  { telnyx_api_key, telnyx_from_number, to, body }
//    Option B (live flow):    { account_id, to, body }
//  Returns: { ok:true, messageId, to } | { ok:false, error:"..." }
// ================================================================

import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";

const CORS = {
  "Access-Control-Allow-Origin":  "*",
  "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
  "Access-Control-Allow-Methods": "POST, OPTIONS",
};

function json(data, status = 200) {
  return new Response(JSON.stringify(data), {
    status,
    headers: { ...CORS, "Content-Type": "application/json" },
  });
}

serve(async (req) => {
  if (req.method === "OPTIONS") return new Response("ok", { headers: CORS });

  try {
    const body = await req.json();

    // ── 1. Resolve credentials ──────────────────────────────────
    let apiKey    = "";
    let fromNum   = "";
    let profileId = "";

    if (body.telnyx_api_key) {
      // Option A — inline creds (from SMS Settings test panel)
      apiKey    = String(body.telnyx_api_key);
      fromNum   = String(body.telnyx_from_number           || "");
      profileId = String(body.telnyx_messaging_profile_id  || "");

    } else if (body.account_id) {
      // Option B — load stored settings from Supabase DB
      const supabase = createClient(
        Deno.env.get("SUPABASE_URL"),
        Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")
      );
      const { data, error } = await supabase
        .from("sms_settings")
        .select("*")
        .eq("account_id", body.account_id)
        .single();

      if (error || !data) {
        return json({
          ok: false,
          error: "No SMS settings found. Configure in App Settings → SMS Settings."
        });
      }
      if (!data.sms_enabled) {
        return json({
          ok: false,
          error: "SMS sending is disabled. Enable it in App Settings → SMS Settings."
        });
      }
      if (data.provider !== "telnyx") {
        return json({ ok: false, error: `Provider '${data.provider}' is not yet supported.` });
      }

      apiKey    = String(data.telnyx_api_key                 || "");
      fromNum   = String(data.telnyx_from_number             || "");
      profileId = String(data.telnyx_messaging_profile_id    || "");

    } else {
      return json({
        ok: false,
        error: "Provide telnyx_api_key directly, or account_id to load stored settings."
      });
    }

    // ── 2. Validate required fields ─────────────────────────────
    if (!apiKey)   return json({ ok: false, error: "Telnyx API key is empty. Check SMS Settings." });
    if (!fromNum)  return json({ ok: false, error: "Telnyx From Number is empty. Check SMS Settings." });
    if (!body.to)  return json({ ok: false, error: "'to' phone number is required (E.164 format: +12125551234)." });
    if (!body.body && body.body !== 0)
                   return json({ ok: false, error: "'body' message text is required." });

    // Warn if number doesn't look like E.164
    const toNum = String(body.to).trim();
    if (!toNum.startsWith("+")) {
      console.warn("[send-sms] 'to' number may not be E.164 format:", toNum);
    }

    // ── 3. Build Telnyx API v2 payload ──────────────────────────
    // https://developers.telnyx.com/docs/messaging/messages/send-message
    const telnyxPayload = {
      from: fromNum,
      to:   toNum,
      text: String(body.body),
      type: "SMS",
    };
    // Messaging Profile ID is optional but recommended for production
    if (profileId) telnyxPayload.messaging_profile_id = profileId;

    // ── 4. Call Telnyx Messages API ─────────────────────────────
    console.log(`[send-sms] Sending to ${toNum} from ${fromNum}…`);

    const telnyxRes = await fetch("https://api.telnyx.com/v2/messages", {
      method:  "POST",
      headers: {
        "Content-Type":  "application/json",
        "Authorization": `Bearer ${apiKey}`,
      },
      body: JSON.stringify(telnyxPayload),
    });

    const telnyxData = await telnyxRes.json();

    // ── 5. Handle Telnyx response ───────────────────────────────
    if (!telnyxRes.ok) {
      // Extract the most useful error message from Telnyx error schema
      const errDetail = telnyxData?.errors?.[0]?.detail
        || telnyxData?.errors?.[0]?.title
        || telnyxData?.detail
        || `Telnyx API error ${telnyxRes.status}`;
      console.error(`[send-sms] ✗ Telnyx error (${telnyxRes.status}):`, errDetail);
      return json({ ok: false, error: errDetail }, 500);
    }

    const messageId = telnyxData?.data?.id    || "";
    const status    = telnyxData?.data?.status || "queued";
    console.log(`[send-sms] ✓ Sent to ${toNum} | id=${messageId} | status=${status}`);

    return json({ ok: true, messageId, to: toNum, status });

  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    console.error("[send-sms] ✗ Uncaught error:", msg);
    return json({ ok: false, error: msg }, 500);
  }
});
4
Deploy the function
One click — takes about 10 seconds
1
Click the Deploy button in the Supabase editor (top-right corner).
2
Wait for the green ✓ Deployed confirmation. The function URL will be shown: https://<project>.supabase.co/functions/v1/send-sms
3
Test it using the panel below — enter your Telnyx API key, from number, and a real mobile number to receive a test SMS.

Live Test — Send a Real SMS

Credentials go to your function only, not stored here

Function deployed — what's next?

  • Go to SMS Settings → enter your Telnyx API key and from-number → save → enable SMS
  • Open the Agent Builder → drag a Send SMS node onto the canvas → fill in To and Message Body fields
  • Use {{variables}} in the SMS body for dynamic content (e.g. Hi {{customerName}}, your booking is confirmed!)
  • The To field also supports {{phoneVar}} to route to the customer's number collected earlier in the flow
  • The node stores the result in a variable (default: smsResult) — use it in a Condition node to branch on success/failure
1
Create the function file
In your project root
mkdir -p supabase/functions/send-sms
# Paste the function code (Step 3 above) into:
# supabase/functions/send-sms/index.ts
2
Login and deploy
One command
supabase login
supabase functions deploy send-sms --no-verify-jwt

API Reference

POST /functions/v1/send-sms Headers: Authorization: Bearer <anon_key>
FieldTypeDescription
account_id OPT A string Load stored Telnyx settings for this account (used by flow runtime)
telnyx_api_key OPT B string Inline Telnyx API Key v2 — starts with KEY0…
telnyx_from_number OPT B string Your Telnyx number in E.164 format, e.g. +12025551234
telnyx_messaging_profile_id string optional Telnyx Messaging Profile UUID — groups numbers, enables webhooks
to required string Recipient number in E.164 format, e.g. +447911123456
body required string Message text. Max 160 chars per segment (Telnyx auto-segments longer messages)
Success response: { ok: true, messageId: "...", to: "+...", status: "queued" }  ·  Error: { ok: false, error: "..." }

SMS Provider Roadmap

Telnyx LIVE
Twilio Coming Soon
Vonage / Nexmo Coming Soon
AWS SNS Coming Soon
MessageBird Coming Soon

Telnyx API Notes & Troubleshooting

E.164 Phone Format is Required

Both from and to must be in E.164 format — a + followed by the country code and number, no spaces or dashes. Examples: +12125551234 (US), +447911123456 (UK), +61412345678 (AU). Telnyx will reject numbers that don't start with +.

Messaging Profile vs Direct Number

You can send without a Messaging Profile — just set the from number directly. However, using a Messaging Profile is recommended for production because it: (1) groups your numbers, (2) enables delivery status webhooks, (3) helps manage rate limits and 10DLC registration for US traffic.

US SMS — 10DLC Registration

For US domestic messaging, carriers require 10DLC (10-Digit Long Code) registration — you must register your brand and campaign in the Telnyx portal. Unregistered traffic is filtered or blocked by US carriers. This is a carrier regulation, not a Telnyx restriction. Registration takes 1–3 business days. See Telnyx 10DLC docs.

Common Error Codes
40003 / Invalid to Phone number not in E.164 format or not valid for SMS
40002 / Invalid from From number not owned by your account or not SMS-capable
40300 / Unauthorized Invalid API key — check KEY0… prefix, no extra spaces
20001 / Filtered US traffic filtered — 10DLC registration required for this route