Building a Local SMS Sync Bridge: Extracting Messages from macOS without Twilio
Over the past development session, I tackled a specific infrastructure problem: reading SMS messages from a Samsung Android device and syncing them to a local macOS environment without external SMS gateway dependencies. This post walks through the technical decisions, architecture patterns, and implementation details.
The Challenge
The initial assumption was that SMS ingestion required Twilio credentials stored in .secrets/repos.env. However, after investigating the codebase, I discovered those credentials weren't available locally. The requirement changed: build a standalone sync mechanism that could pull SMS directly from the source device and integrate with existing message infrastructure.
What Was Done
- Created
/Users/cb/Documents/repos/tools/samsung_sms_sync.py— a Python script for local SMS extraction - Configured a macOS LaunchAgent at
/Users/cb/Library/LaunchAgents/com.cb.samsung-sms-sync.plistfor automated, periodic sync - Integrated with existing JADA SMS export infrastructure instead of rebuilding from scratch
- Mapped Android SMS database schema to local conversation digests
Technical Architecture
Data Source: Android SMS Database
Android devices store SMS in a SQLite database accessible via ADB (Android Debug Bridge). The key files are:
/data/data/com.android.providers.telephony/databases/mmssms.db— main SMS/MMS storage- Tables:
sms(individual messages),threads(conversation groupings),canonical_addresses(phone numbers)
Rather than accessing the device directly via KDE Connect or other bridge tools (which were checked but not ideal for automation), the approach leveraged ADB over USB:
brew install android-platform-tools
adb shell "sqlite3 /data/data/com.android.providers.telephony/databases/mmssms.db 'SELECT * FROM sms ORDER BY date DESC LIMIT 100;'"
This required installing Homebrew's android-platform-tools on Apple Silicon (M1/M2). The install path differs from Intel: /opt/homebrew/bin/adb rather than /usr/local/bin/adb. The Python script accounts for this via platform detection.
Local Message Infrastructure Integration
macOS includes a built-in SMS capability through the Messages app, which is scriptable via AppleScript and accessible to Python through the osascript bridge. However, the more reliable approach was direct SQLite export to a structured SMS digest format:
def extract_sms_thread(phone_number, db_path):
"""Query the Android SMS database for a specific contact."""
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute("""
SELECT date, body, type
FROM sms
WHERE address = ?
ORDER BY date DESC
""", (phone_number,))
return cursor.fetchall()
The type field distinguishes between:
type=1— received messagestype=2— sent messages
Timestamps are in milliseconds since epoch; conversion to human-readable format is necessary for digest output.
LaunchAgent Configuration
The com.cb.samsung-sms-sync.plist file configures automated sync:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.cb.samsung-sms-sync</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/python3</string>
<string>/Users/cb/Documents/repos/tools/samsung_sms_sync.py</string>
</array>
<key>StartInterval</key>
<integer>3600</integer>
<key>StandardOutPath</key>
<string>/var/log/samsung_sms_sync.log</string>
<key>StandardErrorPath</key>
<string>/var/log/samsung_sms_sync_error.log</string>
</dict>
</plist>
StartInterval: 3600 runs the sync every hour. This avoids constant polling while keeping messages relatively fresh for digest operations.
Key Architectural Decisions
Why Not Twilio?
Twilio is excellent for outbound SMS or API-driven workflows, but it requires:
- Active subscription and API credentials
- A phone number registered with Twilio
- Network calls for every message lookup
For local read-only access to historical SMS, direct database extraction is cheaper and faster. The trade-off: requires USB debugging enabled on the Android device and ADB connectivity.
Why ADB Over Other Bridge Tools?
Several alternatives were evaluated:
- KDE Connect: Designed for file sync and clipboard sharing; SMS access not primary use case
- Apple Messages Service: Can send SMS via the Messages app, but doesn't directly expose Android message history
- Mac Messages Database:
~/Library/Messages/chat.dbexists but only contains messages routed through Messages.app, not synced Android conversations
ADB is the Android SDK's standard debugging protocol—it's battle-tested, scriptable, and provides direct database access without intermediate services.
Digest Generation Over Real-Time Sync
Rather than maintaining a bidirectional sync (which is complex and error-prone), the system generates digests on demand:
def generate_sms_digest(phone_numbers, date_range_days=7):
"""Compile recent SMS into a formatted email digest."""
digests = {}
for number in phone_numbers:
messages = extract_sms_thread(number, DB_PATH)
digests[number] = format_thread(messages, date_range_days)
return digests
This approach:
- Reduces complexity — no need to track sync state or handle conflicts