How Meetily's SQLite Database Stores Meeting Data, Transcripts, and Summaries Locally

Meetily persists all meeting‑related information in a local SQLite database managed by the DatabaseManager in its Tauri backend, using migrations for schema version control and WAL mode for reliable writes.

Meetily stores every aspect of your meetings—metadata, transcripts, speaker information, and AI-generated summaries—in a local SQLite database, ensuring complete data privacy and offline access. This architecture centers on the DatabaseManager struct in frontend/src-tauri/src/database/manager.rs, which handles database lifecycle, connection pooling, migrations, and write-ahead logging. Below is a complete breakdown of how Meetily implements this local-first storage system.

Database Location and Initialization

Meetily places the SQLite database file in the user's application-data directory:

  • macOS: ~/Library/Application Support/Meetily/meeting_minutes.sqlite
  • Windows/Linux: Equivalent platform-specific directories

The DatabaseManager::new_from_app_handle method constructs this path, checks for legacy .db files to migrate, creates the database if missing, and immediately applies all pending migrations. This guarantees that the schema always matches the current application version.

// Database initialization sequence (from manager.rs)
let db = DatabaseManager::new_from_app_handle(&app_handle).await?;

Connection Management and Transactions

The DatabaseManager creates a pooled SqlitePool via SqlitePool::connect, which all queries share. For atomic operations, the manager provides with_transaction, an async helper that automatically commits on success or rolls back on failure.

// Example transaction usage pattern
db.with_transaction(|txn| async move {
    // Multiple operations that succeed or fail together
    sqlx::query!("INSERT INTO ...").execute(txn).await?;
    sqlx::query!("UPDATE ...").execute(txn).await?;
    Ok(())
}).await?;

Write-Ahead Logging (WAL) and Durability

Meetily uses SQLite's WAL mode for better concurrency and crash recovery. The DatabaseManager implements specific safeguards:

  • Startup check: Detects and removes corrupted .wal or .shm files, then re-opens the database
  • Shutdown cleanup: Calls PRAGMA wal_checkpoint(TRUNCATE) to flush pending writes and delete WAL files

These measures prevent data corruption and reclaim disk space predictably.

Database Schema and Migrations

All schema changes are version-controlled in frontend/src-tauri/migrations/ and applied via sqlx::migrate!("./migrations"). The migration history reveals how Meetily's data model evolved:

Core Tables (Initial Schema)

20250916100000_initial_schema.sql establishes the foundation:

  • meetings — id, title, start_ts, end_ts
  • transcripts — id, meeting_id, speaker_id, text, ts
  • speakers — id, name

Extended Tables (Subsequent Migrations)

Migration File Purpose
20251101000000_add_summary_backup.sql Creates summaries table for generated meeting summaries
20251223000000_add_meeting_notes.sql Adds meeting_notes table for free-form notes linked to meetings
20251229000000_add_gemini_api_key.sql Stores Gemini API key in settings table
20250920155811_add_openrouter_api_key.sql Adds OpenRouter API key to settings
20251006000000_add_audio_sync_fields.sql Extends meetings with audio_offset and sync metadata
20251010153942_add_ollama_endpoint.sql Ollama endpoint configuration in settings
20251105120000_add_pro_license_custom_openai.sql Licensing data for custom OpenAI endpoints
20251110000001_add_speaker_field.sql Enhanced speaker tracking in transcripts

Storing Meeting Data: Complete Examples

Inserting Meeting Metadata

When a user starts recording, Meetily creates a meeting record:

sqlx::query!(
    "INSERT INTO meetings (title, start_ts) VALUES (?, ?)",
    "Q4 Planning Session",
    chrono::Utc::now().timestamp()
)
.execute(db.pool())
.await?;

Saving Transcript Fragments

As Whisper produces transcription segments, they stream into the database:

sqlx::query!(
    "INSERT INTO transcripts (meeting_id, speaker_id, text, ts) VALUES (?, ?, ?, ?)",
    meeting_id,           // Foreign key to meetings.id
    speaker_id,           // Foreign key to speakers.id (or NULL)
    "Let's review the quarterly metrics.",
    chrono::Utc::now().timestamp()
)
.execute(db.pool())
.await?;

Retrieving and Displaying Data

The frontend fetches transcripts through Tauri commands using ordered queries:

let rows = sqlx::query!(
    "SELECT text, ts, speaker_id FROM transcripts 
     WHERE meeting_id = ? ORDER BY ts ASC",
    meeting_id
)
.fetch_all(db.pool())
.await?;

Storing AI-Generated Summaries

After processing, summaries persist to a dedicated table:

sqlx::query!(
    "INSERT INTO summaries (meeting_id, content, generated_at) VALUES (?, ?, ?)",
    meeting_id,
    "Key decisions: 1) Expand hiring... 2) Delay product launch...",
    chrono::Utc::now().timestamp()
)
.execute(db.pool())
.await?;

Data Flow from Recording to Storage

  1. Audio capture produces separate microphone and system audio streams
  2. Voice Activity Detection (VAD) filters non-speech segments before sending to Whisper
  3. Transcription returns text fragments that the backend inserts into transcripts
  4. Post-processing generates summaries stored in summaries and notes in meeting_notes
  5. Querying happens via Tauri commands that execute SQL against the pooled connection

Key Implementation Files

File Path Responsibility
frontend/src-tauri/src/database/manager.rs DatabaseManager struct: initialization, migrations, pool management, WAL cleanup, transaction helper
frontend/src-tauri/migrations/20250916100000_initial_schema.sql Core tables: meetings, transcripts, speakers
frontend/src-tauri/migrations/20251101000000_add_summary_backup.sql summaries table for meeting summaries
frontend/src-tauri/migrations/20251223000000_add_meeting_notes.sql meeting_notes table
frontend/src-tauri/migrations/*.sql All schema evolution tracked as versioned migrations

Summary

  • Local-first architecture: All data stays on-device in a single SQLite file
  • Versioned schema: Migrations in frontend/src-tauri/migrations/ ensure reliable upgrades
  • Reliable writes: WAL mode with corruption detection and explicit checkpointing
  • Flexible storage: Separate tables for meetings, transcripts, speakers, summaries, and notes
  • Atomic operations: with_transaction helper prevents partial updates

Frequently Asked Questions

Where does Meetily store my meeting data?

Meetily stores everything in a single SQLite file at ~/Library/Application Support/Meetily/meeting_minutes.sqlite on macOS (with equivalent paths on Windows and Linux). This file contains all meetings, transcripts, speakers, summaries, and settings—no cloud required.

How does Meetily prevent data loss if the app crashes?

The DatabaseManager uses SQLite's WAL (Write-Ahead Logging) mode. On startup, it checks for and removes corrupted WAL/SHM files. On shutdown, it runs PRAGMA wal_checkpoint(TRUNCATE) to ensure all data is fully written to the main database file. Transactions also guarantee that multi-step operations complete entirely or not at all.

Can I access my Meetily data from other applications?

Yes—the SQLite file is standard and queryable with any SQLite client. The schema follows migration files in frontend/src-tauri/migrations/, with core tables including meetings, transcripts, speakers, summaries, and meeting_notes. Note that schema changes may occur with app updates.

What happens to my data when Meetily updates?

The DatabaseManager automatically runs sqlx::migrate!("./migrations") on startup, applying any new migration files in version order. This ensures your existing data is preserved while the schema evolves to support new features.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →