Skip to content

Open-source SMS and phone verification

Send SMS through phones you already own.

Pair an Android phone, call one API, and follow every message to the carrier's delivery report.

The Bridge console listing delivered messages and forwarded incoming SMS for a project

One request in. A delivery report out.

Your app talks to Bridge. Bridge picks a phone that can send right now and tells you what the carrier said.

  1. Your app sends

    shell
    curl https://sms.example.com/v1/messages \
      -H "Authorization: Bearer $BRIDGE_API_KEY" \
      -H "Idempotency-Key: order-2291-shipped" \
      -d '{
        "to": "+919876543210",
        "message": "Order ORD-2291 has shipped."
      }'

    An idempotency key means a retried request never sends twice.

  2. Bridge picks a phone

    • Online and under its send limit
    • Charging and on Wi-Fi, preferred
    • Retries on another phone if one fails
    • Wakes sleeping phones with a push
  3. The phone reports back

    realme, Android 14
    Online
    • Charging
    • Wi-Fi
    • Airtel

    Sent, then delivered, as the carrier reports it. Never guessed.

Everything after the API call is handled.

Every status, with evidence

Each message keeps a timeline: which phone took it, which SIM sent it, and what the carrier reported.

  • Fetch it with GET /v1/messages/{id}
  • Or get it pushed as webhooks
  • Bodies redacted after 30 days
msg_06gh9q17gd…Delivered
  1. CreatedPOST /v1/messages
  2. Queuedwaiting for a phone
  3. Phone acceptedrealme, SIM 1 (Airtel)
  4. Sent1 segment, GSM-7
  5. Deliveredcarrier delivery report

Signed webhooks

Deliveries, failures and replies arrive as Standard Webhooks, retried for about three days.

webhook-id: evt_06gh9q…
webhook-signature: v1,K5oZ…

Pair in one scan

Scan the code with the Bridge app. The phone confirms your server before it trusts it.

Incoming SMS, forwarded

Turn on forwarding for a phone and its replies reach your webhooks within seconds.

  • +919812345678Got the parcel, thanks!
  • +14155550132STOP

A test mode that costs nothing

Test keys simulate the whole lifecycle, webhooks included. These numbers fail on purpose:

+15550000001
Delivered
+15550000002
Invalid number
+15550000003
No delivery report
+15550000005
Undelivered

Phone verification in two calls.

Bridge generates the code, sends it from your phones and checks what the user types. Your app never stores, compares or expires a code.

  • Never stored readableOnly a keyed hash, erased once the code is used.
  • Limits built inAttempts, expiry, resend cooldown and an hourly cap per number.
  • AutofillSMS Retriever on Android, WebOTP in Chrome, the domain line in Safari.
  • Free in testsTest keys return the code, so CI never needs a phone.
// When the user asks for a code
await bridge.otp.send({ to: '+919876543210' });

// When they type it in
const { valid } = await bridge.otp.verify({ to: '+919876543210', code });
Pinecartnow

482913 is your Pinecart code. It expires in 10 minutes. Do not share it.@pinecart.in #482913

Sign in to Pinecart

Enter the code sent to +91 98765 43210

482913
From Messages: 482913Continue
The last line lets Chrome and Safari offer the code above the keyboard.

When no phone can send, a provider can.

Keep your phones as the first route and add MSG91, Twilio, Vonage or Plivo behind them. Same API, same message timeline.

Shipped in 0.5. Test keys never reach a provider, so tests stay free. MSG91 sends the DLT-registered templates India requires.

How this project sends messages
  1. Your paired phones first

    A message waits up to 60 seconds for a phone that can send. You set the wait, from 0 to 3600 seconds.

  2. Then your providers

    If no phone takes it, the next enabled provider does, in the order you set. The timeline records why.

    1. 1MSG91
    2. 2Twilio
    3. 3Vonage
    4. 4Plivo

Provider credentials are encrypted with AES-256-GCM under BRIDGE_SECRET_KEY before they reach the database.

Phone login for the auth you already use.

Point Supabase Auth's Send SMS hook at Bridge and your users get their codes from your own phones.

Supabase Auth

Supabase still generates and checks the code. Bridge only delivers the SMS, after checking that the request really came from your Supabase project.

  1. Your appCalls signInWithOtp({ phone }), unchanged.
  2. Supabase AuthGenerates the code and calls the Send SMS hook.
  3. BridgeVerifies the signature and queues the SMS.
  4. Phone or providerDelivers it and reports back.
Supabase dashboard
# Authentication > Hooks > Send SMS hook
Type    HTTPS
URL     https://api.sms.example.com/v1/hooks/supabase/int_06ghc2…
Secret  v1,whsec_…   # generated by Supabase, pasted into Bridge

Use it from anywhere you write code.

A REST API with an OpenAPI spec, a zero-dependency TypeScript SDK, and a CLI that forwards live events to localhost.

curl https://sms.example.com/v1/messages \
  -H "Authorization: Bearer $BRIDGE_API_KEY" \
  -H "Idempotency-Key: order-2291-shipped" \
  -d '{
    "to": "+919876543210",
    "message": "Order ORD-2291 has shipped."
  }'

A console your whole team can read.

Usage by day, request logs, a playground, roles and an audit log. Dark by default, light when you want it.

The Bridge usage page with daily charts of delivered and failed messages

Your server, your phones, your data.

Bridge runs with Docker Compose on any machine that has it. Message text is removed after 30 days by default.

One binary
API, worker and migrations in Go
One database
PostgreSQL holds data and the job queue
AGPL-3.0
Client SDKs are MIT
terminal
git clone https://github.com/kroszborg/bridge.git
cd bridge
docker compose up -d
# dashboard  http://localhost:3000
# API        http://localhost:8080

What Bridge will not pretend.

Not for bulk marketing

Bridge sends the messages your product needs. Carrier rules, DLT registration and consent still apply to you.

A phone sends at a phone’s pace

Android asks before an app sends more than about 30 SMS in 30 minutes. Bridge paces each phone and tells you how to raise it.

Delivery means the carrier said so

A message is delivered only when a delivery report arrives. Without one, it stays sent.

Failures come with a reason

Invalid numbers, no signal, radio off: every failure has a stable error code you can act on.

Providers bill you, not Bridge

Fallback through MSG91, Twilio, Vonage or Plivo is charged by them, at their rates, on your account.

Pre-release, tested on real hardware

Verified on a realme phone running Android 14 on Airtel, delivery reports included. More devices are next.

From your own phones to any provider.

Seven releases are done, all open source and self-hosted.

  1. 0.1

    Android gateway

    Pair a phone, send through its SIM, delivery reports and a message timeline.

    Shipped
  2. 0.2

    Inbound SMS, webhooks, SDK

    Incoming SMS forwarding, signed webhooks and the TypeScript SDK.

    Shipped
  3. 0.3

    Developer platform

    Playground, CLI, usage, request logs, teams, audit log and status page.

    Shipped
  4. 0.4

    Verify API

    One-time passwords with otp.send() and otp.verify(), and a free test mode.

    Shipped
  5. 0.5

    Providers and integrations

    MSG91, Twilio, Vonage and Plivo as fallback. Supabase Send SMS hook, plus guides for Better Auth, Auth0, n8n, Zapier and Make.

    Shipped
  6. 0.6

    Verify Pro

    Verify apps, failover when a code is not sent in time, fraud limits, and a drop-in widget.

    Shipped
  7. 0.7

    Messaging tools

    Bulk and scheduled sends, auto-replies with opt-out, and forwarding to Telegram, email or a phone.

    Shipped

Frequently asked questions

Something missing? Open an issue on GitHub.

What is Bridge?

Bridge is an open-source SMS and phone verification platform that you run on your own server. Your app calls one REST API. Bridge sends each message through an Android phone you paired, or through an SMS provider, and reports what the carrier said.

How is Bridge different from Twilio?

Twilio is a hosted service that sends from its own numbers and bills per message. Bridge is software you host. By default it sends from the SIM cards in your own Android phones, so the cost is your mobile plan. From 0.5, Bridge can also send through Twilio, MSG91, Vonage or Plivo when no phone is available, behind the same API.

Does Bridge work on an iPhone?

Not as a gateway. iOS does not let apps send SMS in the background, so the gateway app is Android only. iPhones receive Bridge messages like any other SMS and can autofill verification codes, because Bridge can add the domain line Safari uses to offer the code above the keyboard.

Can I send SMS in India with DLT registration?

Bridge does not help you bypass DLT. For DLT-registered traffic, add MSG91 as a provider with your approved templates. Bridge passes template variables, such as the one-time code, to MSG91 instead of free text.

What do I need to self-host Bridge?

A machine with Docker Compose, or the Go binary and a PostgreSQL database. Paired phones need to reach the API over a public HTTPS URL. Each phone needs a SIM and the Bridge Android app: the foss build wakes up through UnifiedPush, the gms build through Firebase Cloud Messaging.

How much does Bridge cost?

Nothing. The server, dashboard and Android app are open source under AGPL-3.0, and the client SDKs are MIT. Your carrier still charges for the SMS your phones send, and providers charge their own rates if you use them as a fallback.

Is Bridge ready for production?

Not yet. Bridge is pre-release. Sending and delivery reports are verified on a realme phone running Android 14 on Airtel, and more devices are being tested before a stable release.

How do I call Bridge from my code?

Through the REST API, described by an OpenAPI document that every Bridge server serves at /openapi.json. There is also a zero-dependency TypeScript SDK, @kroszborg/bridge, and a command-line tool, bridgectl.

Made by

Your first SMS is three commands away.

Start in test mode for free, then pair a phone when you are ready.