Problem (Why)
Pushi has no SMS delivery path, so use cases like OTPs, alerts and transactional messages fall outside the platform even though the pub/sub, subscription and routing machinery is already in place. SMS providers vary by region and cost, so hard-wiring a single vendor would force a rewrite later. A pluggable SMS adapter with Twilio as the first backend gives immediate coverage while keeping the door open for additional providers (e.g. Vonage, AWS SNS).
Description (What)
Introduce an SMSHandler adapter with a small provider abstraction so concrete backends can be selected via configuration, shipping Twilio as the first backend. An SMS subscription model stores a destination phone number per subscription, overridable per published event. Twilio credentials (account SID, auth token, from-number) follow the existing App-model + environment-variable configuration pattern.
Implementation (How)
- Define an SMS backend abstraction (a small
SMSBackend base with a send_sms(to, message, **opts) method) plus a TwilioBackend implementation, selected by config, in src/pushi/base/sms.py.
- Add
SMSHandler(handler.Handler) implementing load, add, remove, subscribe, unsubscribe, send(app_id, event, json_d, invalid={}) and a direct send_to_numbers(...); resolve the active backend and honor per-event number overrides over the stored subscription number.
- Call the Twilio Messages REST API via
netius.clients.HTTPClient with HTTP basic auth, consistent with existing HTTP usage.
- Add App-model config fields in
src/pushi/app/models/app.py (sms_provider, twilio_sid, twilio_token, twilio_from) with global env-var fallbacks via appier.conf.
- Add the
SMS subscription model in src/pushi/app/models/sms.py with fields (number, event) and handler-sync lifecycle hooks.
- API: add an
SMSController in src/pushi/app/controllers/sms.py exposing GET/POST /sms and DELETE /sms/<number> (list/subscribe/unsubscribe).
- API client: add
SMSAPI in src/pushi/api/sms.py with create_sms / delete_sms / subscribe_sms / unsubscribe_sms.
- Register the handler in
State.load_handlers() in src/pushi/base/state.py and export new symbols from the four __init__.py files.
- Add tests in
src/pushi/test/sms.py (backend selection, Twilio payload/auth, per-event override, subscription sync) using the mock-owner pattern.
- Update
README.md, add an examples/sms/ example, and add a [Unreleased] CHANGELOG entry.
Problem (Why)
Pushi has no SMS delivery path, so use cases like OTPs, alerts and transactional messages fall outside the platform even though the pub/sub, subscription and routing machinery is already in place. SMS providers vary by region and cost, so hard-wiring a single vendor would force a rewrite later. A pluggable SMS adapter with Twilio as the first backend gives immediate coverage while keeping the door open for additional providers (e.g. Vonage, AWS SNS).
Description (What)
Introduce an
SMSHandleradapter with a small provider abstraction so concrete backends can be selected via configuration, shipping Twilio as the first backend. AnSMSsubscription model stores a destination phone number per subscription, overridable per published event. Twilio credentials (account SID, auth token, from-number) follow the existing App-model + environment-variable configuration pattern.Implementation (How)
SMSBackendbase with asend_sms(to, message, **opts)method) plus aTwilioBackendimplementation, selected by config, insrc/pushi/base/sms.py.SMSHandler(handler.Handler)implementingload,add,remove,subscribe,unsubscribe,send(app_id, event, json_d, invalid={})and a directsend_to_numbers(...); resolve the active backend and honor per-event number overrides over the stored subscription number.netius.clients.HTTPClientwith HTTP basic auth, consistent with existing HTTP usage.src/pushi/app/models/app.py(sms_provider,twilio_sid,twilio_token,twilio_from) with global env-var fallbacks viaappier.conf.SMSsubscription model insrc/pushi/app/models/sms.pywith fields (number,event) and handler-sync lifecycle hooks.SMSControllerinsrc/pushi/app/controllers/sms.pyexposingGET/POST /smsandDELETE /sms/<number>(list/subscribe/unsubscribe).SMSAPIinsrc/pushi/api/sms.pywithcreate_sms/delete_sms/subscribe_sms/unsubscribe_sms.State.load_handlers()insrc/pushi/base/state.pyand export new symbols from the four__init__.pyfiles.src/pushi/test/sms.py(backend selection, Twilio payload/auth, per-event override, subscription sync) using the mock-owner pattern.README.md, add anexamples/sms/example, and add a[Unreleased]CHANGELOG entry.