Work

Connecfy

An SMS gateway built on ordinary Android phones. Each phone’s SIM card is a sender; Connecfy is the queue, the routing and the API in front of them.

Role
Backend, Android client, documentation
Platform
Web API, staff console and Android sender app
Status
Accounts are set up by hand
Links
Documentation
The Connecfy documentation site, showing the introduction page and the path a message takes from the API to a SIM card.
The public documentation at docs.connecfy.com, a separate static site.

Overview

Connecfy sends SMS through real SIM cards in Android phones that act as gateways. A client queues a message from the dashboard or through an API key; a phone picks it up, sends it, and reports back.

There is no self-service signup. Staff create client accounts and pair phones from a separate staff console, and a client either sends through their own paired phones or through a shared pool.

How it fits together

The life of one message
  1. POST /api/sms/send/The message is written as a row with status queued.
  2. GET /api/devices/<id>/tasks/A phone polls for work, roughly every 20 seconds, and atomically claims the row.
  3. SIM → mobile networkThe phone sends the message through its own SIM card.
  4. POST /api/sms/<id>/status/The phone reports sent, delivered or failed.

Separately, every phone sends a heartbeat about every 15 seconds with its battery and signal level.

Engineering notes

Polling on purpose

Nothing in the chain is push-based. Phones ask for work over plain HTTP instead of keeping a socket open, which works on any phone on any network, and means there is no WebSocket server or message broker to run.

Claiming work atomically

SMS.claim_for_device() flips a message from queued to sending with SELECT … FOR UPDATE SKIP LOCKED on PostgreSQL. A duplicate poll can’t be handed a row another poll already holds.

Recovery without a background worker

If a phone claims a message and then disappears, the stale claim is noticed the next time any of that account’s phones polls: the message is requeued, or failed once its retries run out. A device is marked offline the same lazy way, whenever its status is read and its last heartbeat is too old.

A deliberately narrow API

The external API can send a message and nothing else. An API key can’t read message history or device details, so a leaked key can’t be used to read anything.

Staff checks on the server

The staff console lives at its own URL, separate from Django’s admin, and every one of its views checks is_staff on the server, not only in the templates that hide the links.

Stack

Backend
Python, Django, Django REST Framework, PostgreSQL
Clients
Android sender app; server-rendered dashboard and staff console
Security
Device tokens, rate limiting, django-axes
Languages
English and French, with Django i18n
Docs
Static site at docs.connecfy.com