The situation
Msgvault has one message graph but several ways speech arrives inside it: a first-class voice note, a generic audio attachment, voicemail, a meeting recording pointer, or a Loom/Cap URL pasted into any conversation. The words should be searchable the same way regardless of which importer found them.
Msgvault should not become the transcription or remote-download service. Its job is to capture enough source truth, route the exact attachment or protected recording reference to Docbank, retain the returned Docbank identity, and enforce message visibility when results come back. Docbank owns acquisition, Docling/ASR, normalized transcript evidence, embeddings, indexes, and retry state.
What works today, and why it is not enough
The current importers expose most of the raw material, but not one complete route:
| Source |
Current evidence |
Gap before routing |
| Beeper |
Explicit isVoiceNote, audio metadata, duration, downloaded bytes, and sometimes a source transcript |
Preserve full transcript provenance and route missing-transcript media |
| WhatsApp |
Explicit audio and voice_note types, MIME, duration, and optional local media bytes |
Route stored media and expose missing/unavailable media state |
| Slack |
Authenticated audio/* downloads with bounded CAS storage |
Preserve documented media fields such as duration/captions/subtype and route audio |
| Discord |
Attachment MIME, duration, waveform, flags, and downloaded bytes |
Preserve voice-message semantics and route eligible audio |
| Facebook Messenger exports |
Explicit audio-file attachments with local paths |
Route stored audio |
| SyncTech SMS/MMS |
Arbitrary MMS parts with content type and stored bytes |
Route audio MIME parts |
| Microsoft Teams |
Typed call-recording URL plus reference attachments |
Route recording references without downloading them in the Teams sync |
| Google Voice Takeout |
Voicemail records and duration |
Associate and import the voicemail audio when present, then route it |
| iMessage/SMS |
cache_has_attachments only |
Read the attachment join/table, safely resolve local files, retain audio metadata, and store exact bytes |
Beeper source transcripts already enter ordinary message text, so they are searchable. The other paths either archive opaque bytes, retain only a URL/metadata record, or—in iMessage and Google Voice voicemail—do not yet capture the audio at all. Loom and Cap links in ordinary message text have no provider-aware occurrence record.
Ask
Add one provider-complete media router in Msgvault and use the Docbank media-ingestion contract for all extraction and retrieval. Build the current providers as one matrix so each importer cannot drift into its own transcript pipeline.
1. Finish source capture across every current provider
- Add iMessage attachment extraction from the local Messages database and attachment store. Treat database paths and filenames as untrusted, keep reads inside explicitly allowed roots, and preserve the message/attachment join identity. Audio messages must become ordinary stored attachment occurrences rather than a boolean flag.
- Associate Google Voice Takeout voicemail audio with its exact voicemail record when the export contains it. Missing audio remains a typed source gap, not a failed transcript.
- Retain provider facts that affect routing or provenance: Beeper source transcript details, WhatsApp voice-note versus generic audio, Slack duration/caption/file subtype, Discord voice flags/duration/waveform, Teams call-recording identity, and source MIME/type claims. Keep raw payloads authoritative when typed models are incomplete.
- Keep a generic fallback: any current or future importer that stores bytes sniffed as eligible audio/video can enter the same router even when the provider has no first-class voice-note flag. Provider semantics enrich the occurrence; they do not fork processing.
2. Detect recording links in the common message path
- Detect Loom and Cap links after message text normalization across every message provider, not only Slack or Beeper. Also route provider-native recording pointers such as the current Teams call-recording event.
- Store one sanitized recording occurrence per message with provider, trusted origin, canonical URL, protected acquisition URL when needed, provider key when proven, and source message identity.
- Exact public hosts and documented routes identify Loom/Cap Cloud. Self-hosted Cap is recognized only on an operator-registered exact origin. Do not treat arbitrary
/s/<id> URLs as Cap.
- URL detection performs no network request and cannot hold a provider sync cursor.
3. Route; do not process
- A durable asynchronous worker submits either verified attachment bytes or a protected recording reference to Docbank. Msgvault does not call Docling, run ASR, fetch Loom/Cap media, parse player pages, retain generated transcript Markdown, or build transcript vectors.
- The request carries the caller-owned attachment/recording occurrence, source digest when bytes exist, message/source identity, and a named Docbank credential binding when remote acquisition needs one. No credential value or provider token is copied into Msgvault records or job payloads.
- Persist only the stable Docbank source/content-version identity, processing/coverage summary, and enough state to retry or explain why routing did not happen. Do not depend on Docbank's internal rendition, chunk, embedding, or index IDs.
- Reuse a source-provided transcript by submitting it as provider evidence. Docbank runs ASR only when no usable source transcript exists or an operator explicitly selects a replacement profile.
4. Keep sync and lifecycle independent
- Provider sync commits the message and available source bytes first. Docbank disabled, unavailable, unconsented, access-required, unsupported, or temporarily failing cannot stop cursor progress.
- Backfill every existing eligible attachment and recording pointer through the same router. The backfill does not re-download bytes already in Msgvault's CAS.
- Routing is idempotent by occurrence plus source version. Identical bytes shared in multiple messages process once in Docbank and map back to every live occurrence.
- Attachment replacement creates a new source version. Message deletion, hiding, or source revocation removes that occurrence from the next Docbank fence without deleting another live occurrence.
5. Return results as message evidence
- Keyword, semantic, and hybrid transcript search query Docbank with a fresh fence of currently visible content versions, then recheck visibility before presentation.
- Results map back to the owning message and conversation with an excerpt and explicit provenance: authored text, provider transcript/captions, or generated transcript. Do not append generated transcripts to authored message bodies or the legacy ordinary-message vector lane.
- Conversation and attachment views show the transcript beneath the source occurrence plus Docbank coverage/access state. A link-only or access-required recording stays visible without claiming it was archived.
Acceptance criteria
- One production-path provider matrix covers Beeper, WhatsApp, Slack, Discord, Facebook Messenger exports, SyncTech MMS, Teams recordings, Google Voice voicemail, and iMessage/SMS audio. New providers join through the generic stored-media/router contract.
- iMessage audio attachments and exported Google Voice voicemail audio are captured with exact message associations and routed like existing attachment-bearing providers.
- Loom, Cap Cloud, registered self-hosted Cap, and Teams recording references detected in any message source create Docbank submissions without network work in the sync path.
- A source transcript is searchable without ASR. A supported attachment without one becomes searchable after Docbank processing. Both remain distinguishable from authored text.
- The same media shared in multiple messages is processed once and returned for every visible occurrence.
- Disabled/unavailable Docbank, missing media, access-required links, unsupported formats, and provider failures leave precise status while provider sync still succeeds.
- Replacement, deletion/visibility, retry, crash recovery, and source-fence tests prove stale transcript results cannot appear on SQLite or PostgreSQL.
Out of scope
- Any Msgvault-owned downloader for Loom, Cap, or future recording providers.
- Any Msgvault-owned ASR, transcript store, transcript embedding generation, or retrieval index.
- Crawling provider accounts or bypassing recording access controls.
- Speaker/language/timestamp invention when the source and selected Docbank result do not supply it.
Dependencies and references
The situation
Msgvault has one message graph but several ways speech arrives inside it: a first-class voice note, a generic audio attachment, voicemail, a meeting recording pointer, or a Loom/Cap URL pasted into any conversation. The words should be searchable the same way regardless of which importer found them.
Msgvault should not become the transcription or remote-download service. Its job is to capture enough source truth, route the exact attachment or protected recording reference to Docbank, retain the returned Docbank identity, and enforce message visibility when results come back. Docbank owns acquisition, Docling/ASR, normalized transcript evidence, embeddings, indexes, and retry state.
What works today, and why it is not enough
The current importers expose most of the raw material, but not one complete route:
isVoiceNote, audio metadata, duration, downloaded bytes, and sometimes a source transcriptaudioandvoice_notetypes, MIME, duration, and optional local media bytesaudio/*downloads with bounded CAS storagecache_has_attachmentsonlyBeeper source transcripts already enter ordinary message text, so they are searchable. The other paths either archive opaque bytes, retain only a URL/metadata record, or—in iMessage and Google Voice voicemail—do not yet capture the audio at all. Loom and Cap links in ordinary message text have no provider-aware occurrence record.
Ask
Add one provider-complete media router in Msgvault and use the Docbank media-ingestion contract for all extraction and retrieval. Build the current providers as one matrix so each importer cannot drift into its own transcript pipeline.
1. Finish source capture across every current provider
2. Detect recording links in the common message path
/s/<id>URLs as Cap.3. Route; do not process
4. Keep sync and lifecycle independent
5. Return results as message evidence
Acceptance criteria
Out of scope
Dependencies and references