- mailcmd replacement (vm_mailcmd.py): faster-whisper transcription (CPU int8), extractive summary + intent tags + spoken-digit number extraction, multipart/alternative HTML email, fail-safe relay of original message - Telegram DM delivery (vm_telegram.py) with per-mailbox routing - Caller-ID -> name (vm_contacts.py): file / google / carddav backends - SQLite store (vm_store.py) with content-addressed audio - FastAPI portal (vm_web.py): PIN login, list/play/delete, per-user settings, zero JS, loopback-only behind Apache TLS - Backfill importer (vm_import.py) for existing spool recordings - systemd unit, Apache vhost + certbot TLS, install.sh - Docs: INSTALL, CONFIGURATION, ARCHITECTURE, OPERATIONS, SECURITY, TESTING Verified end-to-end on mail.txt3.net: 157 historical messages backfilled, live voicemail -> transcribed -> stored -> visible at https://vm.txt3.net.
5.9 KiB
Configuration
All config lives in /opt/vm-transcribe/. Templates are committed as
*.example; the live files are created by install.sh or by you. Secret-bearing
files (telegram.conf, contacts.conf) are mode 640 root:asterisk and git-
ignored.
Most settings can also be set via environment variables, which is how the systemd unit wires production values. Env vars override file values when both are present unless noted.
Environment variables (shared)
| Variable | Default | Used by | Meaning |
|---|---|---|---|
VM_DB |
/var/lib/vm-transcribe/voicemail.db |
store, web, import | SQLite path. Created with WAL mode if missing. |
VM_AUDIO_DIR |
/var/lib/vm-transcribe/audio |
store, web, import | Where copied recordings live (<sha[:2]>/<sha>.wav). |
VM_LOG |
/var/log/asterisk/vm_mailcmd.log |
mailcmd | Log path. |
VM_MODEL_CACHE |
/opt/vm-transcribe/models |
mailcmd, import | Whisper model download/load dir. |
VM_WHISPER_MODEL |
base.en |
mailcmd, import | Model name. tiny/base/small trade speed for accuracy. |
VM_SPOOL |
/var/spool/asterisk/voicemail |
import | Spool root to scan. |
VM_ASTERISK_CONF |
/etc/asterisk/voicemail.conf |
web, auth | For PIN lookup. |
voicemail.conf (Asterisk)
One line is all the pipeline needs:
mailcmd=/opt/vm-transcribe/venv/bin/python3 /opt/vm-transcribe/vm_mailcmd.py
Place it in the [general] section (or any context). After editing:
sudo asterisk -rx 'voicemail reload'
install.sh backs up the file and adds this line automatically.
telegram.conf
[telegram]
enabled = yes
token = 123456789:AAH... ; from @BotFather
default_chat_id = ; fallback for unmapped mailboxes (blank = none)
send_audio = yes ; voice note with caption
send_transcript = yes ; full transcript as a follow-up message
timeout = 20 ; seconds per send attempt
[mailbox:1001]
chat_id = 123456789 ; single recipient
[mailbox:1002]
chat_id = 5551, 5552 ; comma list fans out to several people
send_transcript = no ; any global key can be overridden per mailbox
[mailbox:1003]
chat_id = -1001234567890 ; negative = group chat
send_audio = no ; text-only DM
Routing rules (evaluated per voicemail):
- Match the
[mailbox:N]section for the voicemail's mailbox number. chat_idmay be a single id, a comma-separated list (fan-out), or a group id (negative).- If no mailbox section matches, fall back to
default_chat_id. - If
enabled = noor no route resolves, nothing is sent — email still goes out. - The file is re-read on every voicemail; no reload needed after editing.
Delivery fallback chain (per chat id, tried in order): sendVoice →
sendDocument → sendMessage (text-only). So a Telegram outage or a bad audio
file still delivers the summary.
Contact resolution note: a user must message the bot once before it can DM them. Telegram does not permit bots to initiate conversations.
contacts.conf
[contacts]
backends = file ; space/comma separated: file google carddav
cache_ttl = 86400 ; seconds; cache hits AND misses
cache_file = /var/lib/vm-transcribe/contacts-cache.json
# --- 'file' backend (recommended): a vCard or CSV export -------------
[file]
path = /var/lib/vm-transcribe/contacts.vcf
# CSV (Google export "All contact data") is auto-detected:
# path = /var/lib/vm-transcribe/contacts.csv
# --- 'google' backend: People API via OAuth ---------------------------
[google]
token_file = /var/lib/vm-transcribe/google-token.json
# Obtain a token with the People API (contacts.readonly) scope. See
# docs/SECURITY.md. No app password here — OAuth only.
# --- 'carddav' backend: app password, Nextcloud/Fastmail/iCloud --------
[carddav]
# Google rejects app passwords (basic auth disabled 2024-09-30); a Google
# URL here is refused with a log warning and the backend is skipped.
url = https://contacts.fastmail.com/dav/addressbooks/user/xxx/Default
username = jp@txt3.com
app_password = ; app-specific password, not your login password
Matching is on the last 9 digits of the caller ID, so +447700900123,
07700900123 and 447700900123 all collapse to the same contact. Names resolve
best-effort; on any backend error the caller ID is used as-is.
Portal settings (per mailbox, in the SQLite store)
These are edited from the portal's Settings page by each user; they are not
config files. Every value defaults to yes.
| Setting | Effect |
|---|---|
summarise |
Run the summariser (if off, the raw transcript is shown). |
telegram |
Forward new voicemails to the mailbox's Telegram route (requires telegram.conf). |
email_notify |
Send the enriched email (if off, the message is stored but not emailed). |
highlight |
Colour the caller chip when a name is resolved from contacts. |
max_inbox |
Keep at most N messages; oldest beyond N are auto-deleted (0 = unlimited). |
vm-portal.service
Production values are passed as environment in the unit file:
Environment=VM_DB=/var/lib/vm-transcribe/voicemail.db
Environment=VM_AUDIO_DIR=/var/lib/vm-transcribe/audio
Environment=VM_ASTERISK_CONF=/etc/asterisk/voicemail.conf
Environment=VM_SESSION_HOURS=12
Login hardening (in-process):
| Var | Default | Meaning |
|---|---|---|
VM_MAX_FAILS |
5 |
Failed attempts per (mailbox, IP) before locking. |
VM_LOCK_MINUTES |
15 |
Lock duration. |
VM_SESSION_HOURS |
12 |
Session lifetime. |
VM_INSECURE_COOKIE |
unset | Set to 1 only for plain-HTTP testing; normally cookies are Secure. |
VM_BASE_PATH |
"" |
Set to e.g. /voicemail if the app is served under a sub-path. |
A service restart clears the in-memory lockout counters. Testing lockout will lock you out of your own verification for
VM_LOCK_MINUTES.