Skip to main content

Send SMS through your own phone's SIM via API — Django app.

Project description

ownsms

PyPI Python Django CI License: MIT

Send SMS through your own phone's SIM card via a simple REST API. ownsms is a Django app that pairs with an Android device; the device long-polls for outbound messages and dispatches them over its own SIM — like Eskiz or Play Mobile, but from your number, at your tariff, with no third-party SMS gateway.

Installation

pip install ownsms

Usage

Add the app to INSTALLED_APPS and include its URLs:

# settings.py
INSTALLED_APPS = [
    ...
    "ownsms",
]

# urls.py
from django.urls import include, path

urlpatterns = [
    path("", include("ownsms.urls")),
]

Migrate, then send an SMS with a Bearer API key:

$ python manage.py migrate
POST /api/v1/messages
Authorization: Bearer osk_<your-api-key>
Content-Type: application/json

{"to": "+998901234567", "text": "Hello from ownsms!"}

The paired Android device polls the server, picks up the message, and sends it over the SIM. See the quickstart for the full register → key → send → status flow.

Bulk campaigns

Send one {var} template to many recipients in a single request — the priority use case for rassilka:

curl -X POST https://sms.omadli.uz/api/v1/campaigns \
  -H "Authorization: Bearer osk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Salom {name}, chegirmangiz kodi {code}",
    "recipients": [
      {"to": "+998901234567", "vars": {"name": "Ali", "code": "A1"}},
      {"to": "+998901112233", "vars": {"name": "Vali", "code": "B2"}}
    ]
  }'
{ "id": "camp_1", "status": "running", "total": 2 }

Validation is fail-fast: one bad phone or missing {var} rejects the whole campaign with the offending rows. Track progress at GET /api/v1/campaigns/camp_1, and pause / resume / cancel via POST /api/v1/campaigns/camp_1/{action}. Pass an idempotency_key to make retries safe. See the quickstart for the full flow.

Features

  • Single send — immediate or queued, with per-message from (SIM), ttl, idempotency_key, callback_url, and send_at scheduling.
  • Campaigns — one template with {var} placeholders + a recipients list, fail-fast validation, idempotency_key for safe retries, progress, and pause / resume / cancel.
  • Delivery lifecyclequeued → sending → sent → delivered | failed, expired, canceled; at-most-once (a job left uncertain by a crash is failed, never resent).
  • Webhooks — HMAC-signed, retried delivery on status transitions (off by default).
  • Per-SIM pacing — rate limits, jitter, working hours, and daily quota, synced to the device.
  • Security — API-key scopes (send / read), revocation, IP allowlist, and an audit log.
  • Sandboxis_test API keys simulate delivery without touching a device.
  • Django admin — every model registered for management.

Full endpoint reference: Swagger UI at /api/v1/docs, schema at /api/v1/openapi.yaml.

Compatibility

Tested on Python 3.11 – 3.14 and Django 4.2 – 5.2 (Django 4.2 and 5.0 require Python ≤ 3.12). Requires Python 3.11+.

Running in production

ownsms is zero-config — it runs with no OWNSMS settings — but two background commands must run on a timer (cron or a systemd timer) every 1–2 minutes:

python manage.py ownsms_housekeeping   # expire TTL-passed messages, reclaim dead leases
python manage.py ownsms_webhooks       # deliver pending webhooks

Without ownsms_housekeeping, messages left behind by a dead or offline device stay queued forever instead of expiring or being reclaimed. Without ownsms_webhooks, status webhooks never fire.

Behind a reverse proxy

ownsms reads the client IP from the X-Real-IP header for the API-key IP allowlist. Your proxy must set it to the real peer and must not let clients spoof it. With nginx:

proxy_set_header X-Real-IP $remote_addr;

Without this, the allowlist sees the proxy's own address (or a forged value) and rejects legitimate requests.

Configuration

All settings live in a single OWNSMS dict in settings.py; every key is optional. The ones a self-hoster usually tunes:

OWNSMS = {
    "POLL_BATCH_SIZE": 25,         # jobs handed to a device per poll
    "LEASE_SECONDS": 300,          # seconds a handed-out job is held before reclaim
    "DEFAULT_TTL_SECONDS": 86400,  # drop a queued message after this long
    "DEVICE_ONLINE_SECONDS": 60,   # device counts as online if seen within this window
}

Keep POLL_BATCH_SIZE and LEASE_SECONDS matched to device pacing: the phone sends jobs one at a time with anti-spam jitter, so a large batch with a short lease reclaims still-waiting jobs as failed before the phone can send them.

Development

pip install -e ".[dev]"
pytest                 # tests
ruff check src tests   # lint
ruff format src tests  # format
tox                    # full Python × Django matrix

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ownsms-0.3.7.tar.gz (45.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ownsms-0.3.7-py3-none-any.whl (47.5 kB view details)

Uploaded Python 3

File details

Details for the file ownsms-0.3.7.tar.gz.

File metadata

  • Download URL: ownsms-0.3.7.tar.gz
  • Upload date:
  • Size: 45.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for ownsms-0.3.7.tar.gz
Algorithm Hash digest
SHA256 e5b06282d17913ad4c2e493bbc9135d4765e61e7225df7e0d221731400a58392
MD5 a1876802ed749a7709e24e5cafe1196b
BLAKE2b-256 c0f3f66f77cbc1d992a04299479ea0d235d802fe19fe8abb90a1bb51913ae521

See more details on using hashes here.

Provenance

The following attestation bundles were made for ownsms-0.3.7.tar.gz:

Publisher: publish.yml on ownsms/django-ownsms

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ownsms-0.3.7-py3-none-any.whl.

File metadata

  • Download URL: ownsms-0.3.7-py3-none-any.whl
  • Upload date:
  • Size: 47.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for ownsms-0.3.7-py3-none-any.whl
Algorithm Hash digest
SHA256 a2f39730777d65ccd973e0b6665ac6d53b96ce0334f3849617c7222ab8b0a35d
MD5 fc48b8fc5ba68fd603229b81d5baa55b
BLAKE2b-256 7274fb1bad0e4a9e697a22f100387de15b0d77d152107b62340d68b645337941

See more details on using hashes here.

Provenance

The following attestation bundles were made for ownsms-0.3.7-py3-none-any.whl:

Publisher: publish.yml on ownsms/django-ownsms

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page