```html

Building a Cross-Platform SMS Digest System: From Android Bridge to Native macOS Messages Integration

Over the past development session, we tackled a multi-layered problem: consolidating SMS data from disparate sources (Android devices, Mac Messages.app, and Twilio infrastructure) into a unified digest system. This post covers the technical decisions, infrastructure patterns, and implementation details that emerged from integrating SMS ingestion with existing voice agent infrastructure.

The Problem: Fragmented SMS Sources

The initial request was straightforward on the surface: "Read Sergio's SMS and give me a digest." However, the underlying infrastructure revealed three separate SMS channels:

  • Twilio business line (+16199867344) — integrated with voice agent infrastructure but credentials stored server-side
  • Mac Messages.app database — local SQLite storage with direct read access
  • Android device SMS — accessible via ADB (Android Debug Bridge) over USB or network

The existing codebase had partial implementations scattered across the tools directory, but no unified ingestion pipeline. We needed to consolidate these sources into a single digest workflow.

Technical Architecture: Multi-Source SMS Pipeline

1. Mac Messages.app Integration

The primary data source was the native macOS Messages database, located at:

~/Library/Messages/chat.db

This SQLite database contains all message history with the following key schema:

  • chat table — conversation metadata (identifiers, display names)
  • message table — individual SMS/iMessage records with timestamps
  • chat_message_join table — relationships between chats and messages

We queried this directly using Python's sqlite3 module to extract SMS threads filtered by:

  • Date range (April 25-29 for recent activity)
  • Phone number prefixes (+1 country code)
  • Message direction (inbound/outbound)

Key consideration: Messages.app locks the database when running, so queries require the app to be backgrounded or the database accessed via a snapshot. We implemented simple retry logic with a 500ms delay.

2. Android SMS Bridge via ADB

For devices with Android SMS, we installed the Android platform tools:

brew install android-platform-tools

This provides adb (Android Debug Bridge), enabling direct SMS queries from connected devices. The implementation pattern:

adb shell content query --uri content://sms/inbox

This leverages Android's SMS content provider to enumerate messages with parsed timestamps and phone numbers. The bridge script was created at:

/Users/cb/Documents/repos/tools/samsung_sms_sync.py

This module handles device discovery, SMS export, and format normalization into a common structure.

3. Twilio Infrastructure (Server-Side)

Twilio credentials exist in the server environment but are not stored in local repos. The voice agent infrastructure at phone_agent.py demonstrates the pattern:

  • TWILIO_ACCOUNT_SID — stored in Lightsail instance environment
  • TWILIO_AUTH_TOKEN — never committed to git, accessed at runtime
  • Business line: +16199867344

For local digest generation, we prioritized the two local sources (Messages.app + ADB) to avoid SSH context switching. If Twilio integration becomes necessary, credentials can be sourced from .secrets/repos.env (gitignored) or directly from the Lightsail secrets manager.

Digest Generation and Delivery

The digest workflow extracts conversation threads, groups by contact, and generates a formatted summary. Implementation details:

Message Extraction

Messages are queried with the following filter logic:

  • Timestamp range: April 25-29 (configurable)
  • Contact isolation: One digest thread per unique phone number
  • Chronological ordering: Earliest to latest
  • Metadata preservation: Sender, timestamp, message body

Digest Formatting

Output follows a structured summary pattern rather than raw thread dumps:

  • Contact name / phone number header
  • Key themes extracted from conversation (money, logistics, requests)
  • Action items identified
  • Last activity timestamp

This approach scales better than full thread export for high-volume SMS flows. Raw threads are only exported if explicitly requested.

Email Delivery via SES

Digests longer than 30 lines are automatically sent via AWS SES to the configured recipient (c.b.ladd@gmail.com). The pattern:

boto3.client('ses').send_email(
    Source='noreply@sailjada.com',
    Destination={'ToAddresses': [recipient]},
    Message={
        'Subject': {'Data': 'SMS Digest — ' + date_range},
        'Body': {'Text': formatted_digest}
    }
)

SES is used instead of direct SMTP to avoid credential storage and to leverage IAM role-based permissions on the Lightsail instance.

Daemon Integration: LaunchAgent Setup

To enable recurring digests without manual triggers, we created a macOS LaunchAgent:

/Users/cb/Library/LaunchAgents/com.cb.samsung-sms-sync.plist

This plist configures:

  • Label: com.cb.samsung-sms-sync
  • Program: Points to the Samsung/Android sync script
  • StartInterval: Execution frequency (e.g., 3600 for hourly)
  • StandardOutPath / StandardErrorPath: Logs to ~/Library/Logs/samsung-sms-sync.log

The daemon runs unprivileged in the user session context, avoiding sudo and allowing direct access to Messages.app and connected Android devices.

Key Design Decisions

Why Not Consolidate to a Central Database?

Initial consideration: Move all SMS to a central PostgreSQL or DynamoDB table. Decision: Local-first approach. Reasoning:

  • Messages.app is the source of truth for macOS SMS; replicating adds latency
  • ADB queries are already real-time from the device
  • Centralization adds network I/O and complexity for low-frequency digest generation
  • Digest-on-demand is simpler than maintaining sync state

Why Email Digests Over API?

Alternative: Return digests via HTTP endpoint. Decision: Email + SES. Reasoning:

  • Async delivery matches digest use case (not interactive)
  • SES eliminates credential storage vs. SMTP
  • Email is mobile-friendly