Skip to content

Back HTTP SMS and delivery-report listings with SMSStore instead of process globals #19

Description

@Justinabox

Summary

The HTTP server's SMS and delivery-report listing endpoints are backed by process-global lists instead of the existing SMS storage layer. SMSService can persist received SMS through SMSStore, but GET /sms/messages returns only server.py's in-memory received_messages list populated by the current process's callback.

That makes the HTTP API lose visible SMS/report history across process restarts and allows unbounded list growth during long-running Raspberry Pi deployments.

Evidence

Affected code:

  • server.py:22-25 declares process-global webhook_urls, received_messages, and delivery_reports lists.
  • server.py:119-123 returns those lists directly from GET /sms/messages and GET /sms/delivery-reports.
  • server.py:171-187 appends new SMS/report events to the globals.
  • callstack/sms/service.py:169-170 and callstack/sms/service.py:210-212 already save sent/received SMS through SMSStore, but the HTTP listing endpoint does not read from that store.
  • Existing tests cover SMSStore and SMSService, but there are no endpoint tests proving restart-safe HTTP listing behavior.

Scout reproduction/static probe on main (1c883c59575676fe6ce28214f27aeded692769b8):

PYTHONPATH=. uv run --no-project --with aiohttp --with pyserial-asyncio python - <<'PY'
from server import create_app, received_messages, delivery_reports

class FakeSMS:
    async def list_messages(self):
        raise RuntimeError('modem SMS store should have been queried')
    async def send(self, to, body):
        raise RuntimeError('not used')
class FakeUSSD:
    async def send(self, code, timeout=15.0):
        raise RuntimeError('not used')
class FakeModem:
    sms = FakeSMS()
    ussd = FakeUSSD()

app = create_app(FakeModem(), api_keys=['test-key'])
print('server globals initially:', {'received_messages': received_messages, 'delivery_reports': delivery_reports})
print('registered routes:', sorted(str(route.resource) for route in app.router.routes()))
print('GET /sms/messages handler source: returns in-memory received_messages without querying modem.sms.list_messages')
PY

Actual output:

server globals initially: {'received_messages': [], 'delivery_reports': []}
registered routes: ['<PlainResource  /sms/delivery-reports>', '<PlainResource  /sms/delivery-reports>', '<PlainResource  /sms/messages>', '<PlainResource  /sms/messages>', '<PlainResource  /sms/send>', '<PlainResource  /sms/subscribe>', '<PlainResource  /ussd/send>']
GET /sms/messages handler source: returns in-memory received_messages without querying modem.sms.list_messages

Repository health checks from this scout run:

git diff --check
PYTHONPATH=. uv run --no-project --with pytest --with pytest-asyncio --with pytest-aiohttp --with pyserial-asyncio --with aiosqlite pytest tests/ -q
# 307 passed in 4.24s

Duplicate searches run before filing:

gh issue list --state all --search "HTTP sms messages in-memory restart SMSStore"
gh issue list --state all --search "delivery reports in-memory restart"
gh pr list --state all --search "HTTP sms messages in-memory restart SMSStore"
gh pr list --state all --search "delivery reports in-memory restart"
# no results

Expected behavior

The HTTP API should expose durable message/report history suitable for unattended Raspberry Pi use:

  • Received SMS listed after process restart if a persistent SMSStore is configured.
  • Sent SMS and delivery-report state can be correlated with the persisted sent message/reference when possible.
  • Lists are bounded/paginated rather than growing forever in memory.

Actual behavior

GET /sms/messages and GET /sms/delivery-reports return process-local globals. Restarting the server resets them to empty, and a long-running process appends indefinitely.

Suggested fix direction

  • Add an HTTP-facing service method or store query for received/sent messages instead of returning received_messages.
  • Persist delivery reports or update persisted sent-message status when a delivery report arrives.
  • Add pagination/limits for list endpoints.
  • Keep in-memory test stores available, but make the server entry point use an explicit SQLite-backed store path/config.

Acceptance criteria

  • GET /sms/messages can return messages persisted before an HTTP server restart when SQLite storage is configured.
  • Delivery reports are persisted or reflected in stored sent-message status.
  • List endpoints support a safe default limit and optional pagination/filtering.
  • Endpoint tests cover restart/recreate-app behavior and avoid unbounded global-list dependence.

Verification gates

git diff --check
PYTHONPATH=. uv run --no-project --with pytest --with pytest-asyncio --with pytest-aiohttp --with pyserial-asyncio --with aiosqlite pytest tests/ -q

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions