Meeting to Opportunity
Flow: Trigger → Pull meetings → Match → Synthesize → Publish (α) → Auto-contact (β, opt-in) → Proactive next-step nudges (opt-in).
Input
- Optional: time window via argument (default = last 24h).
- Optional: pre-declared mode (Default / Silent / Proactive).
Output
- α (default): One
Meeting recapupdate per matched deal, summarizing key points + commitments + next steps. Body carries theGenerated by Claude · <ISO timestamp>footer +<!-- claude-skill-id: meeting-to-deal --><!-- meeting-id: <id> -->comments. AMeeting sync — <Mon DD>doc lists what was matched, what wasn't, and unresolved attendees. - β (Auto-contact, opt-in): New items on the Contacts board for meeting attendees not already in CRM, linked back to the matched deal. Each new contact carries
Source = Claudeon a status column. - Proactive extension:
create_notificationto deal owner for commitments due this week. Stage column edits and last-touch updates are also OK in Proactive (or in Default with batched confirm) when a clear transition phrase is present in the transcript ("we agreed to move to procurement review", "they signed today"). Surfaced as a single batched plan: "<N> stage transitions detected. Apply all / review each / skip stage edits?". Amount-column writes still forbidden.
Knowledge
- Meeting → deal matching heuristic (§ Step 4).
- Update body convention (§ Step 6).
- Shared artifact conventions (§ Shared patterns).
Tools (MCP)
get_user_context— user identity + email domain for filtering meetings and extractinginternal_domain.get_notetaker_meetings— recent meetings + transcripts.search/list_workspaces/get_board_info— locate Deals + Contacts boards.get_board_items_page— fetch active deals to match against.get_column_type_info— resolve email/company columns by type.create_update— write the recap onto the matched deal.create_item— β auto-contact on the Contacts board.change_item_column_values— populate the new contact's email + company columns, writeSource = Claudeon contacts, and apply confirmed stage / last-touch edits on matched deals. Never for amount columns.create_notification— proactive next-step nudges.create_doc— sync summary doc.all_monday_api— escape hatch for connect-board column writes (linking contact ↔ deal). Note: connect-board writes viaall_monday_apimay fail silently — see Error handling reference.
Cross-skill handoffs
- From workspace-builder / daily-briefing: suggested as a way to keep deals fresh after a meeting-heavy day.
- To data-cleanup: if >30% of attendees couldn't be matched, suggest running
data-cleanupto clean contact data. - To daily-briefing: the synced updates feed tomorrow's brief.
Step 0: Connector check + NoteTaker availability
Goal: Confirm the monday connector works and detect whether NoteTaker is available. Also extract internal_domain for use in Steps 4–5.
- Try
mcp__monday__get_user_context. On error → standard install prompt, stop. - Extract
internal_domainfrom the user's email (e.g."[email protected]"→"monday.com"). Store for Steps 4–5. If email is absent from the response,internal_domain="monday.com"(safe default). - Try
mcp__monday__get_notetaker_meetings({ limit: 1 }). - If NoteTaker returns results → set
notetaker_available: true. Continue to Step 1. - If NoteTaker returns auth error, permission error, or empty-by-permission → set
notetaker_available: false. Do not stop. The skill will collect meeting content from the user directly in Step 3.
PAUSE: do not write any
create_*call before Step 6.
Step 1: Detect mode
- Default — bundle all writes (recaps + stage edits + last-touch updates + auto-contacts) into a single batched confirm before executing.
- Silent — skip the batched confirm; β auto-contact still requires explicit opt-in.
- Proactive — ask once: "Proactive mode will: (a) write recaps to matched deals, (b) auto-create missing contacts, (c) apply stage/last-touch edits when transcripts are unambiguous, (d) notify deal owners on commitments due this week. Amount columns stay untouched. Approve all / review each batch / deny stage edits only?". Cache the choice.
If undeclared, Default.
Hard safety rail regardless of mode: no deletes, no amount-column writes, no cross-workspace moves. Stage transitions are bundled into a batched plan — never written one-off, never silently.
Step 2: Resolve time window
- If argument carries a phrase, parse: "today" / "yesterday" / "last N days" / "since YYYY-MM-DD".
- Default = last 24h.
- If no argument was provided AND 0 meetings are found (or on the very first run with no prior session context), print: "Using last 24h (no window specified). Add 'yesterday', 'last 7 days', or 'since YYYY-MM-DD' to change the range."
- If parsed window > 14 days: confirm via
AskUserQuestion(large windows produce noisy recaps).
Step 3: Pull meetings
3a — NoteTaker path (notetaker_available: true)
mcp__monday__get_notetaker_meetings({ since: <ISO>, until: <ISO>, limit: 50 }). Cache transcripts + attendees + meeting metadata.
Edge cases:
- 0 meetings in window: don't stop — fall through to Step 3b to let the user provide meeting content manually. Print: "No NoteTaker meetings found in
<window>. You can paste meeting notes or describe what happened instead." - >50 meetings: paginate up to 200, then cap and surface sampling note.
- Tool 429: backoff 3x; on third failure fall through to Step 3b.
3b — Manual input path (notetaker_available: false, OR NoteTaker returned 0 meetings)
Ask the user once via AskUserQuestion:
"No NoteTaker recordings available. How would you like to provide meeting notes?"
- (a) Paste the transcript or notes here — I'll read them from your next message
- (b) Point me to a file — provide the path and I'll read it
- (c) Describe what happened — tell me in free text (attendees, key points, commitments, next steps)
- (d) Skip — nothing to log right now
If (d) → stop with: "No meetings logged. Run this skill again when you have notes to add."
For (a) / (b) / (c): treat the user-provided content as a single meeting entry. Structure it as:
title: first line of the notes, or ask if blankdate: today (or extract from notes if mentioned)attendees: extract from notes, or ask oncetranscript_text: the full provided content
Cache as one entry in meetings[] with source: "manual" and continue to Step 4. The matching and recap flow works identically — the transcript is just from the user's input instead of NoteTaker.
Step 4: Resolve Deals + Contacts boards (Gather)
Resolve the boards directly — this skill is self-contained:
- Deals board:
get_user_context→ scanfavorites+relevantBoardsfor names matchingdeals|opportunities|pipeline|sales. One candidate → use it; multiple →AskUserQuestion; zero →list_workspaces→search("deal", BOARD). Still zero → degrade per step 4 below. - Contacts board: same pass for
contacts|people|leads(only if β auto-contact may run). If Contacts board is missing and mode = Proactive and β was approved, surface a chat-level message before writing the sync doc: "Auto-contact was approved but I couldn't find a Contacts board. Contacts not created. Run/monday-crm:workspace-builderto set one up." Then continue with α recap only. get_board_infoon each. Resolve columns by type:- On Deals:
email(if present),textcompany-name,peopleowner,statusstage,numbersvalue,datelast-touch. - On Contacts:
email,text(or company-linkboard_relation),phone,peopleowner.
- On Deals:
- If Deals board missing entirely: degrade — skip α, write only the sync summary doc with "no Deals board found" header. Suggest
/monday-crm:workspace-builder.
Step 5: Match meetings → deals (Synthesize)
Goal: For each meeting, find the single best matching deal — or flag it as unmatched.
internal_domain was extracted in Step 0. Use it throughout this step in place of hard-coded @monday.com checks.
For each meeting:
- Extract signals: attendee emails (excluding
@<internal_domain>addresses), company names from email domains, meeting title keywords. - Pull active deals (Pass A from daily-briefing — non-Won/Lost),
limit: 500. - Score candidates:
- +5 per attendee email matching a deal's email column.
- +3 per attendee email domain matching a deal's company-name column (case-insensitive substring).
- +2 if meeting title contains a deal name (≥3 contiguous chars match).
- +1 if deal owner is a meeting attendee — only when ALL non-internal attendees are from
@<internal_domain>(i.e. purely internal meeting). This signal is intentionally narrow: a deal owner being on an external sales call is expected and not a reliable match signal; it only carries weight for internal deal-review meetings where ownership is the strongest available signal.
- Pick the top score if it's ≥5 AND beats the second-best by ≥3. Otherwise flag as ambiguous (multiple candidates) or unmatched (no candidate ≥5).
- Multi-deal meetings: if top two candidates are within 2 points and both ≥5, flag as multi-match — write the recap to both deals with a header line "Meeting also discussed: <other deal>".
Edge cases:
- Internal-only meetings (all attendees
@<internal_domain>) and no deal-name title match: skip (likely standup), log to summary doc as "skipped: internal-only". - Personal-meetings filter: skip meetings tagged 1:1 (no transcript content of CRM relevance).
Step 6: Synthesize the recap (Synthesize)
For each matched meeting, compose a structured recap from the transcript:
- Key points (3–5 bullets, factual — what was discussed).
- Commitments made (who → what → by when, parsed from "I'll send you...", "we'll follow up by...", "next week we...").
- Open questions (statements ending with
?or signaled by "we need to figure out", "TBD"). - Stage transition signal: if transcript contains procurement / legal / contract / close phrases, propose a stage change. Mark each as
confidence: high|lowbased on (a) explicit phrase ("we agreed to <stage>", "signed today") = high, (b) inferred ("seems like they're ready for legal review") = low. Onlyhightransitions enter the batched plan;lowones surface as text recommendations.
Deal-side metadata lookup: pull current stage + value + close date for context (single get_board_items_page filtered to the matched item ID).
Step 7: Publish α — per-deal recap (Publish)
For each matched meeting + deal pair:
-
Idempotency check: Use
all_monday_apiwith the following query pattern to scan existing updates on the deal item:Walk the
bodyof each returned update and check for both<!-- claude-skill-id: meeting-to-deal -->and<!-- meeting-id: <id> -->. If a match is found → useall_monday_apito update that existing update's body; do not post a duplicate.Note:
mcp__monday__searchcannot search update bodies — always useall_monday_apiitems_by_id for this check. -
create_update({ itemId: <dealId>, body: <recap markdown> }). -
Update body shape:
Batched confirm (Default mode)
Before any write, print a single plan: "I'll write: <N> recaps · <S> stage edits (high-confidence only) · <L> last-touch updates · <C> new contacts. <U> meetings unmatched (will list separately). <K> low-confidence stage signals will surface as text only. Proceed? (yes / show plan in detail / no)."
If user picks "show plan in detail":
- List all stage transitions by name:
<deal name> · <old stage> → <new stage>for every planned stage edit. - Show one example recap (the first deal).
- Then confirm: "Apply all? (yes / skip stage edits / no)".
Never per-deal confirms beyond that.
Auto-contact cap warning: If unmatched-attendee count > 25, add to the confirm message: "<N> new contacts detected (> 25 cap). I'll create 25 and add the rest to a review-list doc. Proceed?" (e.g. "31 new contacts detected (> 25 cap). I'll create 25 and add the rest to a review-list doc. Proceed?")
Step 8: β — auto-create missing contacts (opt-in)
Goal: Don't make the user manually add every meeting attendee to Contacts.
Only runs if mode = Proactive (option a) OR Default-mode user opts in at Step 7 confirm (added prompt: "+ create N missing contacts on Contacts board?").
For each unique attendee email not found on the Contacts board:
create_item({ boardId: <contactsBoard>, itemName: "<First Last>", groupId: "new" }).change_item_column_valuesto populate email, company (from email domain), andSource = Claudeon the board's source/status column. If no source/status column exists, prompt the user once: "Add aSourcestatus column on Contacts so we can flag plugin-created records? (yes / skip)". If skipped, contacts are still created but source isn't tracked.- Link the new contact to the matched deal via the
board_relationcolumn usingall_monday_api. If this call fails (connect-board writes can fail silently), log the contact as "contact created, deal link pending — set manually" in the sync doc rather than stopping. Do not retry silently.
Cap at 25 contacts per run. Above that, batch into a review-list doc rather than creating silently. (Flag this at Step 7 confirm time — see auto-contact cap warning above.)
Step 9: Proactive next-step nudges (opt-in)
Only runs if mode = Proactive AND session-level approval was given.
For commitments parsed in Step 6 with a parseable due-date within next 7 days:
create_notification({ userId: <deal owner>, text: "Commitment due <date> on <deal>: <commitment>" }).- If
create_notificationrequires anitemId: use the sync doc's item ID (from Step 10'screate_docreturn value). Do not pin the notification to an arbitrary deal item when the doc covers cross-deal commitments.
Hard rail: never write commitments to amount columns. Notifications only for proactive nudges (stage edits already handled in Step 7's batched plan, not here).
If >20 nudges queued, batch into one This week's commitments doc (with <!-- claude-skill-id: meeting-to-deal --> in body) and notify once with a link to the doc.
Step 10: Sync summary doc + close the loop
create_doc (idempotent — same-day update-in-place):
One-line chat summary: Synced <N> meetings to <M> deals. <K> unmatched, <L> contacts created. Doc: <url>.
Shared patterns
- No prefix on titles. Findability via
Generated by Claudefooter +<!-- claude-skill-id: meeting-to-deal -->comment in update/doc bodies;Source = Claudeon contact items. - Embedded meeting-id comment + skill-id comment for idempotency — search-and-update, never duplicate.
- Safety rail. No deletes, no amount-column writes, no cross-workspace moves. Stage edits go through the batched plan; low-confidence transitions stay as text recommendations only.
- Type-based column resolution for non-English boards.
- Batched confirms (Step 7) — never per-meeting prompts.
- Internal-only meeting filter — skips standups, 1:1s, retro meetings.
internal_domainfrom Step 0 used throughout for internal/external attendee classification — not hard-coded to@monday.com.- Skill-id comment uses legacy name
meeting-to-opportunity— kept for backward compatibility with existing updates/docs carrying<!-- claude-skill-id: meeting-to-opportunity -->. The skill's invocation name is/monday-crm:meeting-to-deal.
Output template
Per-deal recap (create_update body)
Sync summary doc
Chat one-liner:
Error handling reference
Completion criteria
- Step 0 (connector + NoteTaker) ran and passed.
internal_domainextracted. - Each matched deal got at most one new (or one updated) recap update (verified via
all_monday_apiitems_by_id update-body scan). - Every recap body carries
<!-- claude-skill-id: meeting-to-deal -->+<!-- meeting-id: <id> -->+Generated by Claudefooter. - Sync summary doc exists (or printed to chat on failure).
- Safety rail held: no deletes, no amount-column writes, no cross-workspace moves.
- Stage edits (if any) ran inside the Step 7 batched plan; all stage transitions listed by name in "show plan in detail"; low-confidence transitions stayed as text only.
- Contacts created (if β ran) carry
Source = Claude; failed deal links logged in sync doc as "pending — set manually". - Auto-contact cap warning shown at confirm time if unmatched attendees > 25.
- Notifications (if Proactive) pinned to sync doc item ID, not arbitrary deal items.

