9.3 KiB
9.3 KiB
SMS Backup Viewer (SBV) - Technical Specification
A privacy-focused, self-hosted web application for viewing SMS, MMS, and call log backups from the Android "SMS Backup & Restore" application.
Overview
SBV provides a modern conversation-based interface for browsing message backups while keeping all data local. Key design principles:
- Privacy-first: No telemetry, no remote servers, 100% self-hosted
- Per-user isolation: Each user has their own SQLite database
- Familiar UI: Conversation threading similar to native messaging apps
- Media support: Inline display of images, videos, and contact cards
Technology Stack
Backend
- Go 1.25 with Echo v4 web framework
- SQLite with FTS5 for full-text search
- bcrypt for password hashing
- libheif (optional) for HEIC image conversion
- ffmpeg for 3GP video support
Frontend
- React 19 with React Router 7
- Vite 7 for building
- Bootstrap 5 / React Bootstrap for styling
- Axios for API communication
Deployment
- Docker with multi-stage Alpine-based builds
- Docker Compose for easy deployment
- GitHub Actions for CI/CD to GitHub Container Registry
Project Structure
sbv/
├── main.go # Entry point, route setup, server config
├── internal/ # Core backend logic
│ ├── auth.go # User/session management
│ ├── auth_handlers.go # Auth API endpoints
│ ├── database.go # SQLite initialization, queries
│ ├── handlers.go # Message/call API endpoints
│ ├── parser.go # XML backup file parsing
│ ├── models.go # Data structures
│ ├── middleware.go # Auth middleware
│ ├── settings.go # User preferences
│ ├── heic_enabled.go # HEIC conversion (with libheif)
│ └── heic_disabled.go # HEIC fallback (without libheif)
├── frontend/
│ ├── src/
│ │ ├── App.jsx # Main app with routing
│ │ ├── components/ # React components
│ │ │ ├── ConversationList.jsx # Contact/conversation list
│ │ │ ├── MessageThread.jsx # Message display
│ │ │ ├── Calls.jsx # Call log view
│ │ │ ├── Search.jsx # Full-text search
│ │ │ ├── Upload.jsx # XML file upload
│ │ │ ├── Activity.jsx # Timeline view
│ │ │ ├── LazyMedia.jsx # Lazy-loaded media
│ │ │ ├── MediaGrid.jsx # Media gallery
│ │ │ └── ...
│ │ └── contexts/
│ │ └── AuthContext.jsx # Auth state management
│ └── dist/ # Production build output
├── Dockerfile # Multi-stage build
├── docker-compose.yaml # Deployment config
└── build.sh # Local build script
Architecture
Authentication Flow
- User registers with username/password (bcrypt hashed)
- Login creates a session stored in the auth database
- Session ID stored in httpOnly cookie (
sbv_session) - Middleware validates session on protected routes
- Each user gets a unique database file:
sbv_[user-uuid].db
Data Model
Auth Database (sbv.db):
users- User accounts with hashed passwordssessions- Active sessions with expirationsettings- Per-user JSON preferences
Per-User Database (sbv_[uuid].db):
messages- Unified table for SMS, MMS, and callsrecord_type: 1=SMS, 2=MMS, 3=Calltype: Message direction (1=received, 2=sent, etc.)media_data: BLOB storage for attachments
messages_fts- FTS5 virtual table for search
Message Import Pipeline
- User uploads XML file via
/api/upload - Server saves to temp file, returns immediately
- Parser reads XML incrementally:
- SMS messages parsed with metadata
- MMS messages parsed with parts/attachments
- Call logs parsed with duration/type
- Records inserted with unique constraint (idempotent)
- Client polls
/api/progressfor status
Media Handling
Media is stored as base64-encoded BLOBs in the messages table:
- Images: JPEG, PNG, GIF, WebP, HEIC (converted to JPEG)
- Videos: MP4, 3GP (converted to MP4 via ffmpeg)
- vCards: Parsed and rendered as contact previews
The frontend uses lazy loading (LazyMedia.jsx) to fetch media on-demand.
API Reference
Authentication (Public)
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/register |
Create account |
| POST | /api/auth/login |
Login, sets cookie |
| POST | /api/auth/logout |
Logout, clears cookie |
User (Protected)
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/auth/me |
Current user info |
| POST | /api/auth/change-password |
Update password |
| GET | /api/settings |
Get user settings |
| PUT | /api/settings |
Update user settings |
Messages & Data (Protected)
| Method | Endpoint | Query Params | Description |
|---|---|---|---|
| GET | /api/conversations |
start_date, end_date |
List conversations |
| GET | /api/messages |
address, start_date, end_date, limit, offset |
Messages for conversation |
| GET | /api/activity |
start_date, end_date, limit, offset |
Timeline of messages + calls |
| GET | /api/calls |
start_date, end_date |
Call log |
| GET | /api/search |
q, start_date, end_date |
Full-text search |
| GET | /api/media |
address |
All media for conversation |
| GET | /api/media-items |
address |
Media items only (no data) |
| GET | /api/daterange |
- | Min/max dates in database |
Upload (Protected)
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/upload |
Upload XML backup (async) |
| GET | /api/progress |
Check upload status |
System
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/health |
Health check |
| GET | /api/version |
App version |
Database Schema
messages table
CREATE TABLE messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
record_type INTEGER NOT NULL, -- 1=SMS, 2=MMS, 3=Call
address TEXT, -- Phone number
body TEXT, -- Message text
type INTEGER, -- Direction (1=recv, 2=sent)
date INTEGER, -- Unix timestamp (ms)
read INTEGER, -- Read status
thread_id INTEGER, -- Conversation thread
contact_name TEXT, -- Resolved contact name
-- MMS-specific
msg_id TEXT, -- MMS message ID
m_type INTEGER, -- MMS type
media_type TEXT, -- MIME type
media_data BLOB, -- Binary media
-- Call-specific
duration INTEGER, -- Call duration (seconds)
-- Additional metadata
service_center TEXT,
protocol INTEGER,
subject TEXT,
...
);
-- Key indexes
CREATE UNIQUE INDEX idx_message_unique ON messages(...);
CREATE INDEX idx_address ON messages(address);
CREATE INDEX idx_date ON messages(date);
CREATE INDEX idx_thread ON messages(thread_id);
CREATE INDEX idx_record_type ON messages(record_type);
-- Full-text search
CREATE VIRTUAL TABLE messages_fts USING fts5(
body, address, contact_name,
content='messages', content_rowid='id'
);
Configuration
Environment Variables
| Variable | Default | Description |
|---|---|---|
PORT |
8081 |
Server port |
DB_PATH_PREFIX |
. |
Database directory |
PUID |
1000 |
Docker user ID |
PGID |
1000 |
Docker group ID |
Build Tags
| Tag | Description |
|---|---|
fts5 |
Enable full-text search (always used) |
heic |
Enable HEIC image conversion (requires libheif) |
User Settings
Stored per-user as JSON:
{
"conversations": {
"show_calls": true
}
}
Build & Development
Local Development
# Backend (with hot reload)
air
# Frontend (with hot reload)
cd frontend && npm run dev
Frontend dev server runs on :5175 and proxies API calls to :8085.
Production Build
# With HEIC support
./build.sh
# Without HEIC
go build -tags fts5 -o sbv .
# Frontend
cd frontend && npm run build
Docker
docker compose up -d
The multi-stage Dockerfile:
- Builds frontend with Node.js 22
- Compiles Go binary with FTS5 + HEIC
- Creates minimal Alpine runtime with ffmpeg/libheif
Security Considerations
- httpOnly cookies: Session tokens not accessible to JavaScript
- SameSite=Lax: CSRF protection
- bcrypt hashing: Secure password storage
- Per-user isolation: Users cannot access other users' data
- No internet exposure recommended: Designed for local/trusted networks
Known Limitations
- Large imports: Processing slows with many media attachments
- MMS group sender: Contact names unavailable (XML limitation)
- 100k message limit: Use date filters for older conversations
- Android only: Requires SMS Backup & Restore app (no iOS support)