---
title: Set up SMS integration
---

# Set up SMS integration

## What it is

Omniscio can send and receive text messages using your own Android phone — no Twilio, no separate SMS number. Once connected, Omniscio notifies you by SMS when a session needs your attention, and any reply you send from your phone shows up inside the app.

There are **two ways to connect**, chosen under **Settings → SMS → "SMS provider"**:

- **Pushbullet** (default) — the easiest path. Texts are mirrored through the Pushbullet cloud using the Pushbullet Android app. Supports pictures (MMS) and GIFs. Free Pushbullet accounts cap SMS at 100/month.
- **Native (my phone)** — texts go straight over your phone's real SIM via a free, open-source Android app ([capcom6 "SMS Gateway for Android"](https://github.com/capcom6/android-sms-gateway)) that runs a tiny server on your home network, with **no third-party cloud** in the path and no monthly cap. It is **text-only for now** — pictures, MMS, and GIFs aren't sent or received under Native yet, so use Pushbullet if you need those.

You can switch providers anytime; conversations and history are shared, so nothing is lost.

## Where to find it

SMS lives on its own page inside Settings — open Settings and choose SMS (settings search reaches it too). At the top you pick how your texts travel: Pushbullet, which mirrors them through the Pushbullet cloud, or Native, which sends straight over your own phone's SIM through a small server running on the phone. Choosing a provider reveals a short setup wizard underneath, and you can switch between them at any time without losing your conversations.

Once connected, your text threads appear in Omniscio's Inbox and you can reply from inside the app. When SMS is switched on but stops working, a single alert appears in your Inbox along with one desktop notification, and the switch that controls that alert sits on the same SMS page.

## How it behaves

### Set up Pushbullet (the default)

1. **Open Settings → SMS**, leave the provider on **Pushbullet**. You'll see a three-step wizard: Token → Device → Connect.
2. **Get a Pushbullet API token.** Install Pushbullet on your Android phone and sign in. Then visit `pushbullet.com/account` on desktop, scroll to "Access Tokens", and click "Create Access Token". You'll get a string that starts with `o.` — copy it.
3. **Paste the token into Settings.** Click "Save & Verify". Omniscio confirms the token, encrypts it, and stores it on disk. Then it lists your Pushbullet devices — pick the Android phone marked "SMS capable".
4. **Wait for "Connected".** Omniscio opens a WebSocket to Pushbullet and the status dot turns green. Send yourself a test SMS from another number — it should appear in Omniscio's Inbox within a few seconds.

Re-entering your token or re-picking your device automatically refreshes the connection, and a socket that sticks mid-handshake recovers on its own within ~15 seconds — so you rarely need to click **Retry** if you hit "Taking longer than expected" at Step 3. If the token is rejected, double-check you copied the whole string including the `o.` prefix.

### Set up Native (your own phone, self-hosted)

Native runs a tiny web server **on your phone** that texts flow through — nothing touches anyone else's cloud.

1. **Install the app.** On your Android phone, install the free **SMS Gateway for Android** app (from F-Droid or the project's GitHub release — it isn't on the Play Store because Google restricts SMS-forwarding apps). On **Android 15+**, open the app's **App info → ⋮ → "Allow restricted settings"** once so it can run its local server in the background.
2. **Run it in Local Server mode.** The app shows a local address like `http://192.168.1.5:8080` plus a username and password — that's your phone's own SMS API on your Wi-Fi.
3. **In Omniscio, Settings → SMS**, switch **SMS provider** to **Native (my phone)**. Fill in the phone address, username, and password from the app, and set a **webhook secret** (any strong phrase — you'll paste the same one into the phone app).
4. **Point the phone app's webhook at Omniscio.** Omniscio shows an **Inbound webhook URL** to copy into the app's webhook settings, and to sign each request with that shared secret. This requires **Mobile Access** (Settings → Remote Access) to be on so your phone can reach Omniscio.
5. **Test it.** Text yourself from another number — it should land in Omniscio's Inbox. Send a reply from Omniscio — your phone sends it over your SIM.

**Off Wi-Fi:** the local address only works while your phone and PC share a network. To send/receive when you're away, put both on a private tunnel like Tailscale (free) and use the tunnel address, or use the phone app's own cloud relay — see the app's docs.

### If SMS goes offline

If SMS is turned on but stops working, Omniscio raises a single **"SMS is offline"** alert in your Inbox plus one desktop notification, rather than failing silently — for Pushbullet, that's a saved token that can no longer be read (for example after a hardware or Windows change that resets saved keys) or a connection that drops for a sustained stretch; for Native, it's missing connection settings. A missing/unreadable credential alerts right away (it won't fix itself); a dropped Pushbullet connection waits out a short grace window first, so a brief blip or a laptop sleep doesn't nag you. The alert clears itself automatically once SMS reconnects. Turn it off under **Settings → SMS → "Alert me when SMS goes offline"** (on by default). The watchdog ([/src/main/services/sms/sms-health-monitor.ts](/src/main/services/sms/sms-health-monitor.ts)) ticks once a minute; its behaviour is locked by [sms-channel-health-contract.md](/.claude/memory/contracts/sms-channel-health-contract.md).

### Keyboard support

SMS and Telegram triage in Omniscio is deliberately **mouse-first** — every action in the messaging panes (archive, snooze, mark-as-read, mark-as-spam, reply) is a visible button, and none of them is keyboard-only. That's a declared decision, not an omission, for two reasons:

- **You're usually on your phone.** The whole point of SMS/Telegram integration is replying from your phone; the in-app panes are a convenience, not the primary workspace.
- **The app-wide shortcut set stays clean.** Omniscio's global session shortcuts (J/K to move between sessions, R to reply in the focused session, E to archive, H to snooze) are built around the Claude session list and composer. Messaging threads deliberately don't mirror that layout, so wiring a second, conflicting shortcut set onto them would fight the app-wide keys.

Recorded as a declared keyboard-light exemption to the "never ship shortcut-less by omission" rule (audit `universal-feature-compliance` finding F011, 2026-08-10).

## For agents

### How it works

A small **provider seam** ([/src/main/services/sms/sms-provider.ts](/src/main/services/sms/sms-provider.ts)) reads your `smsProvider` setting and routes every send / status / connect call to the live backend, so the rest of Omniscio doesn't care which provider is active. Both providers feed the **same** incoming pipeline — dedup, spam filter, notifications, automations — so your Inbox, threads, and history behave identically either way.

- **Pushbullet** ([/src/main/services/sms/pushbullet-service.ts](/src/main/services/sms/pushbullet-service.ts)) — outbound calls Pushbullet's `POST /texts` (your phone sends over your carrier, so texts count against your phone plan, not Pushbullet's); inbound is a WebSocket to `wss://stream.pushbullet.com` (three event types: `sms_changed` pushes, `tickle` fetch signals, mirrored notifications), plus a 30-second background sync that catches anything the socket missed — and when that sync sees the newest message in a thread is one _you_ sent, it clears the thread's inbox attention (locked by [sms-inbox-attention-contract.md](/.claude/memory/contracts/sms-inbox-attention-contract.md)). Credentials (`pushbulletApiToken`, `pushbulletDeviceIden`) are encrypted with Electron `safeStorage`.
- **Native** ([/src/main/services/sms/native-sms-service.ts](/src/main/services/sms/native-sms-service.ts)) — outbound POSTs to the phone app's local `/message` API; inbound is an **HMAC-signed webhook** (`POST /sms/native/inbound` on Omniscio's web-access server) that Omniscio verifies before accepting a text. There's no socket to keep open (the phone pushes to Omniscio), so "connected" just means "configured", and there's no device list or monthly quota. The phone password + webhook secret are encrypted at rest and never leave the main process. Behaviour is locked by [native-sms-contract.md](/.claude/memory/contracts/native-sms-contract.md).

**Phone-number storage is canonical (E.164).** Every write to `sms_conversations` or `sms_messages` routes the phone string through [`canonicalizePhoneNumber()`](/src/main/services/phone-utils.ts) (backed by `libphonenumber-js`) before the row lands in SQLite — for BOTH providers. That way a raw 10-digit number typed in the compose dialog (`5551234567`) and the canonical form a sync/webhook returns (`+15551234567`) end up under the same `phone_number` primary key — one thread per person, matching what your phone's Messages app already does. Short carrier codes (3–6 digits with no leading `+`, like `22395`) stay raw so libphonenumber doesn't mangle them. A lint test [tests/unit/lint/sms-canonical-phone-key-coverage.test.ts](/tests/unit/lint/sms-canonical-phone-key-coverage.test.ts) enforces that every SMS-write caller imports the helper, and migration v247 backfills any legacy non-canonical rows on startup.

## Related

- [snooze-a-session.md](snooze-a-session.md) — temporarily hide sessions from the inbox
- configure-notifications.md — route session alerts to SMS, desktop, or email (stub)
- set-up-pushbullet-account.md — Pushbullet account + device prerequisites (stub)
