Files
asterisk-voicemail/docs/CONFIGURATION.md
jp 857284abbf Initial build: Asterisk voicemail transcription + portal
- 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.
2026-08-13 09:31:41 +01:00

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):

  1. Match the [mailbox:N] section for the voicemail's mailbox number.
  2. chat_id may be a single id, a comma-separated list (fan-out), or a group id (negative).
  3. If no mailbox section matches, fall back to default_chat_id.
  4. If enabled = no or no route resolves, nothing is sent — email still goes out.
  5. The file is re-read on every voicemail; no reload needed after editing.

Delivery fallback chain (per chat id, tried in order): sendVoicesendDocumentsendMessage (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.