{
  "source_file": "0930",
  "source_platform": "Dev.to",
  "author": "Tae Kim",
  "title_listed": "Missed calls, missed revenue: the local-first phone assistant I'm building",
  "keywords": "",
  "domain": "机器学习",
  "content_type": "深度技术解析",
  "is_paid_flag": "否",
  "url": "https://dev.to/hannune/missed-calls-missed-revenue-the-local-first-phone-assistant-im-building-2086",
  "publish_date": "2026-08-22",
  "corpus_stage": "",
  "notes": "",
  "id": "c269bf4dff01",
  "collected_at": "2026-10-02T14:03:53",
  "title": "Missed calls, missed revenue: the local-first phone assistant I'm building - DEV Community",
  "body_markdown": "Previous posts in this series covered the infrastructure:\nrunning three AI models sequentially on one server\nand\nwhy I run speech-to-text locally instead of calling a cloud API\n. This one is about the business problem I'm building toward.\nThe problem\nI run a solo consulting practice. Client communication arrives over three channels: phone calls (including voicemails I check hours later), messaging apps, and email. None of these talk to each other.\nAfter a busy stretch, I found myself doing a manual audit: scrolling back through four or five apps, trying to reconstruct what commitments I'd made, who I hadn't responded to, what questions were still open. It's time-consuming and I kept missing things.\nThe expensive miss is a phone call. Someone calls, leaves a voicemail, doesn't follow up in email. If I'm mid-task when the call comes in and don't process the voicemail until the next day, that lead has likely moved on. Industry data suggests small businesses miss 40–60% of incoming calls. I don't know exactly what my number is, but I've been that business.\nThe tools I evaluated each handle one channel. Transcription services for calls. Email summary tools for inbox. Nothing I found reads all three and produces a single output. The integration gap is where the problem actually lives.\nWhat I'm building\nThe goal is a daily summary: here's who contacted you, here's what they wanted, here's what's still open, here are the likely follow-ups.\nThe infrastructure pieces are working individually. Whisper running locally handles call transcription—audio stays on my server, never goes to an external API. bge-m3 handles embeddings for semantic search. Gemma handles images and screenshots. All running sequentially on one box.\nWhat I'm building is the layer on top: intake → process → report.\nTwo problems I've been figuring out this week:\n1. What the output should actually contain\nBefore building the generator, I spent time designing the output format. A daily report isn't useful if it's just a list of transcripts. The items that actually need to surface:\nMissed contacts: someone reached out and I haven't replied\nCommitments: explicit statements I made that need follow-through (\"I'll send that over by Friday\")\nScheduling candidates: date/time mentions that might be pending appointments\nOpen questions: things they asked that I said I'd look into\nThe hard part wasn't picking the categories—it was making each one specific enough to actually implement. \"Show me missed follow-ups\" is ambiguous. \"Flag any contact where they reached out and I have no outbound reply within 48 hours\" is implementable. I went through the format item by item and rewrote vague categories as concrete conditions.\n2. Linking the same person across channels\nIf someone calls Monday and emails Tuesday, those land in two separate systems with no shared ID. To produce a per-contact summary, I need to link them.\nThis is an entity resolution problem—the same class of problem I work on in my main client work, building ER pipelines for corporate data. The structure is identical: match records that don't share a unique identifier.\nFor comms data, the linking fields are usually phone number or email address. Complications:\nPeople use multiple numbers (mobile, work, home)\nEmail aliases (\njohn.smith@company.com\nand\njsmith@company.com\n)\nDisplay names don't match across apps\nMy current sketch: normalize phone numbers (strip formatting, expand country codes), match email with tolerance for common alias patterns, and handle name variants as a fallback signal rather than a primary match key. I haven't implemented this yet—this is still the design phase.\nWhere things stand\nWorking:\nLocal transcription (Whisper), local embeddings (bge-m3), local VLM (Gemma). All running sequentially on one server. Audio, documents, and images stay local.\nBuilding:\nThe pipeline that takes call transcripts + email content + message content and produces the daily report. The output format is designed. The entity resolution approach is sketched. Nothing is integrated end to end.\nThe gap between \"pieces working individually\" and \"system producing something I'd actually use daily\" is what I'm working through now. Next concrete step: get the report generator to produce output I'd actually want to read, not just a proof of concept that technically runs.\nPreviously in this series:\nRunning three AI models on one local server\n|\nWhy local STT instead of a cloud API",
  "metadata": {
    "title": "Missed calls, missed revenue: the local-first phone assistant I'm building - DEV Community",
    "description": "Previous posts in this series covered the infrastructure: running three AI models sequentially on one... Tagged with ai, python, selfhosted, productivity.",
    "keywords": "ai, python, selfhosted, productivity, software, coding, development, engineering, inclusive, community",
    "author": null,
    "og:type": "article",
    "og:url": "https://dev.to/hannune/missed-calls-missed-revenue-the-local-first-phone-assistant-im-building-2086",
    "og:title": "Missed calls, missed revenue: the local-first phone assistant I'm building",
    "og:description": "Previous posts in this series covered the infrastructure: running three AI models sequentially on one...",
    "og:site_name": "DEV Community",
    "og:image": "https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1587560699334-bea93391dcef%3Fw%3D1200%26h%3D630%26fit%3Dcrop",
    "twitter:site": "@thepracticaldev",
    "twitter:creator": "@HannuneTech",
    "twitter:title": "Missed calls, missed revenue: the local-first phone assistant I'm building",
    "twitter:description": "Previous posts in this series covered the infrastructure: running three AI models sequentially on one...",
    "twitter:card": "summary_large_image",
    "twitter:widgets:new-embed-design": "on",
    "twitter:image:src": "https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1587560699334-bea93391dcef%3Fw%3D1200%26h%3D630%26fit%3Dcrop"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [],
  "images": [],
  "audio": [],
  "videos": [],
  "links": [
    {
      "href": "https://dev.to/hannune/running-three-ai-models-on-one-local-server-when-your-vram-doesnt-cover-all-of-them-b7g",
      "label": "running three AI models sequentially on one server",
      "type": "a"
    },
    {
      "href": "https://dev.to/hannune/why-i-run-speech-to-text-locally-instead-of-calling-a-cloud-api-59j7",
      "label": "why I run speech-to-text locally instead of calling a cloud API",
      "type": "a"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:49:38Z",
  "source_url": "https://dev.to/hannune/missed-calls-missed-revenue-the-local-first-phone-assistant-im-building-2086",
  "papers": [],
  "actual_video_files": [],
  "body_chars": 4448,
  "expected_body_images": 0,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/c269bf4dff01/pages/source.html",
  "local_html": "attachments/c269bf4dff01/pages/article.html",
  "body_base_path": "attachments/c269bf4dff01/pages/",
  "body_markdown_path": "attachments/c269bf4dff01/pages/article.md"
}