Turn sales call recordings into a CRM-ready debrief: key objections, deal size and action items, plus BANT qualification, deal health, coaching tips and a follow-up email draft. Built on Gemini audio, with an Express API and Notion logging.
recording.mp3 ─► Gemini transcribes (speakers + timestamps) ─► Gemini extracts insights (JSON schema)
─► deal math + health score ─► Markdown / JSON / CSV ─► Notion page (optional)
| Objections | Category (price, budget, timing, authority, competitor, security...), exact buyer quote + timestamp, severity, how the rep answered, resolved or not, and a better answer to use next |
| Deal size | Amount, currency and period as spoken; annual value and total contract value computed in code (e.g. $4,500/mo × 12), seats, the basis and the quote it came from |
| Action items | Task, owner (seller/buyer), due date resolved from the call date ("next Thursday" → 2026-11-05), priority |
| Deal intel | BANT, decision makers and process, competitors, timeline, risks, stage, sentiment, win probability |
| Deal health | A rule-based 0-100 score (BANT coverage, open objections, next meeting, sentiment, competition) with its reasons, as a second opinion next to the model's win probability |
| Follow-up email | Recap email that answers the open objections and confirms next steps |
Outputs: <date>-<call>.md (readable report), .json (everything), .transcript.txt, .actions.csv, and a folder-wide calls.csv index for Excel or CRM import.
npm install
node bin/call-summarizer.js demo # offline sample, no keys needed -> output/demo/
cp .env.example .env # add GEMINI_API_KEY (free: https://aistudio.google.com/apikey)
node bin/call-summarizer.js doctor # checks Node, Gemini and Notion
node bin/call-summarizer.js summarize recordings/acme-2026-10-28.mp3Requires Node 20+.
call-summarizer summarize <files or folders...> # alias: run
-o, --out <dir> output folder (default output/)
-c, --context <text> what you sell (better objection answers + email); default SELLER_CONTEXT
-d, --date <yyyy-mm-dd> call date; default: date in file name, else file time
-l, --language <name> spoken-language hint, e.g. German
--output-language <name> language of the summary/email
--currency <code> when a number is said without a currency
--notes <text> rep notes added to the prompt
-n, --notion [--replace] create a Notion page (replace = recreate an existing one)
--refresh | --no-cache redo Gemini calls / skip the cache
--mime <type> force a MIME type for odd extensions
-f, --format md,json,csv,txt --no-write --json --concurrency <n>
call-summarizer transcribe <files...> # transcript only
call-summarizer demo [--notion] # sample call; --notion tests your Notion setup
call-summarizer serve [--port 3000] # Express API + upload page
call-summarizer doctor
call-summarizer notion-setup # adds the recommended columns to your database
call-summarizer history [--json]Inputs: audio mp3 wav m4a aac ogg opus flac aiff, video mp4 mov webm mkv (Zoom/Meet/Teams exports), or existing transcripts txt md vtt srt json (these skip transcription). Files up to 14 MB are sent inline; larger ones go through the Gemini Files API automatically (deleted right after).
Cache: transcripts are cached by audio hash + model, analyses by transcript + options, in data/cache/. Re-running a call is free, and changing --context only redoes the analysis, not the transcription.
- Create an internal integration at https://www.notion.so/profile/integrations and copy the secret into
NOTION_TOKEN. - Create a database (any title column works), open
...→ Connections → add your integration. - Put the database URL or ID in
NOTION_DATABASE_ID, then runnotion-setupto add the columns:Company, Call Date, Call Type, Stage, Deal Size, Annual Value, Currency, Win Probability, Health Score, Health, Sentiment, Objections, Open Objections, Action Items, Next Step, Next Meeting, Competitors, Summary, Source, Call ID. summarize <file> --notion. Each call becomes a page with the debrief, objections (with quotes and suggested answers), action items as checkboxes, BANT, the follow-up email and the full transcript.
Only columns that exist with the right type are filled, so you can delete the ones you don't want. Pages are de-duplicated by Call ID (--replace recreates). If Notion fails, the local report is still written and the command exits with code 1.
| Method | Path | Body |
|---|---|---|
GET |
/ |
Drag-and-drop upload page |
POST |
/api/calls |
multipart file + optional context, callDate, language, outputLanguage, currency, notes, notion=true, replace=true |
POST |
/api/calls/text |
JSON { "transcript": "...", "title": "...", "callDate": "2026-10-28", ... } |
GET |
/api/calls?limit=50 |
Recent calls |
GET |
/health |
Status |
Add ?format=md for a Markdown answer or ?transcript=1 to include the transcript in the JSON. Set API_TOKEN before exposing the server; clients then send Authorization: Bearer <token> (or x-api-key).
curl -H "Authorization: Bearer $API_TOKEN" -F file=@call.mp3 -F notion=true http://localhost:3000/api/callsThis makes it easy to plug into Zapier, Make or n8n (for example: new Zoom recording → POST to /api/calls → Notion + Slack).
Docker: docker build -t call-summarizer . && docker run -p 3000:3000 --env-file .env -v calls:/data call-summarizer
src/gemini.js– Gemini REST client: inline or resumable Files API upload, retries on 429/5xx (honoursretryDelay), one switch toGEMINI_FALLBACK_MODELon overload or daily quota.src/prompts.js– transcription prompt, extraction rules and the JSONresponseSchema.src/analysis.js– cleans the model output, does the deal math and the health score (the model never does arithmetic).src/transcript.js– VTT/SRT/JSON transcript import and talk ratio.src/notion.js– schema-aware properties, page blocks (2,000-char chunks, 100-block batches), dedup,notion-setup.src/pipeline.js,src/store.js,src/format.js,src/server.js,bin/call-summarizer.js.
npm test51 tests with node:test. All Gemini and Notion calls are faked, so no keys are needed.
MIT