feat(partners): add twenty-partner-match skill (#21601)

## Summary

- Adds `twenty-partner-match` — a Claude Code skill that closes the
partner pipeline loop: query validated partners from the API, score and
explain candidates against a lead's match criteria, pause for human
validation, then generate and open all intro emails in Gmail (1 client
notification + 2N partner emails for N confirmed partners)
- Updates `twenty-partner-design-doc` to distinguish **default
zero-inference mode** (strict, 1-page brief) from `--full` inference
mode, and adds **Step 8** which always produces
`partner-match-criteria.md` alongside the brief
- Updates `design-doc-doctrine.md` with the full zero-inference /
full-mode doctrine so the Claude Code skill and a future in-product
`defineSkill` stay in sync

## Skill chain

```
/twenty-lead-intro-call-summary → /twenty-partner-design-doc → /twenty-partner-match
```

`/twenty-partner-match` chains back into the earlier skills if
`partner-match-criteria.md` is missing, and applies critical review if
the brief is thin before querying the API.

## Credentials

The skill reads `~/.twenty/credentials.env` for API keys (never
committed). See `SKILL.md` for setup instructions.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21601?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Rashad Karanouh
2026-06-15 18:16:23 +04:00
committed by GitHub
parent 03f9ace0bc
commit 09694b2f3b
3 changed files with 268 additions and 9 deletions
@@ -1,6 +1,6 @@
---
name: twenty-partner-design-doc
description: Use when turning a qualified Twenty lead — a call-summary brief plus any client braindump/docs — into an implementation design doc a partner can scope and quote from. Trigger when pointed at a lead folder (e.g. partners-experience/<LEAD>/) and asked to "draft a design doc", "translate this into Twenty terms", "scope this for a partner", or "prep the partner handoff" for a discovery-qualified prospect. Chains after twenty-lead-intro-call-summary.
description: Use when turning a qualified Twenty lead — a call-summary brief plus any client braindump/docs — into a partner brief a partner can scope and quote from. Trigger when pointed at a lead folder (e.g. partners-experience/<LEAD>/) and asked to "draft a design doc", "create a partner brief", "translate this into Twenty terms", "scope this for a partner", or "prep the partner handoff". Default output is zero-inference (only what the client said; empty sections get a placeholder). Pass --full for inference mode. Chains after twenty-lead-intro-call-summary.
trigger: /twenty-partner-design-doc
---
@@ -16,6 +16,23 @@ Turn a qualified lead's materials into a **design doc**: a translation of the cu
- If there is a raw transcript but no brief, run **twenty-lead-intro-call-summary** first — this skill chains after it.
- Read everything. Convert `.docx` with `textutil -convert txt "<file>" -output /tmp/out.txt` (macOS) or an equivalent extractor.
## Default: zero-inference partner brief
The default output is a **zero-inference partner brief**: only what the client explicitly said, nothing invented.
- `🔮 inf.` **never appears.** If you feel the urge to use it, that line should not exist.
- Required sections with no grounded content get: `> ⬜ Not discussed on call — needs input before this section can be filled.`
- Data-model table: **no Source column**; only rows for objects the client named; only fields the client named; `—` for empty cells.
- Target length: 1 page. Each section 15 lines max.
- Step 2: extract stated facts only. Step 4: verify only sections with grounded content.
- Step 7 self-check: `🔮 inf.` anywhere = failure (remove the whole inference); Source column = failure; doc >2 pages = warning.
- Save as `YYYY-MM-DD-<lead>-partner-brief.md`.
- See "Default behavior" in `design-doc-doctrine.md` for the full rules.
## Mode: `--full` (inference-enabled design doc)
Pass `--full` when discovery is complete and a richer inferred model is useful. Enables `🔮 inf.` tags, adds the Source column to the data model, fills gaps with inferences, and allows longer output. Save as `YYYY-MM-DD-<lead>-design-doc.md`. See "Full mode" in `design-doc-doctrine.md`.
## Steps
1. **Gather** — read all source materials in full. Coverage is the point.
@@ -24,7 +41,14 @@ Turn a qualified lead's materials into a **design doc**: a translation of the cu
4. **Verify load-bearing claims live** — use **WebFetch** against the Twenty doc map in the doctrine's Verification section before asserting any capability. Build the §11 appendix as you go.
5. **Reconcile discrepancies** — sources that disagree (call vs braindump; a name differing across/within sources) get flagged both ways, never silently resolved.
6. **Resolve ❓ with the operator** — after a full v1 draft, use **AskUserQuestion** to ask the Twenty team member the unknowns a Twenty insider can answer; leave customer-facing unknowns as ❓. **If running autonomously** (no operator — a subagent/batch run), skip the questions and leave every unknown as ❓ in the body and §11.
7. **Self-check, then save** — scan the output for: an em dash; a bare `~`; first-person voice outside customer quotes; local file paths; a header that isn't the four-field table; **any flag that isn't one of the four canonical emoji-and-text pairs** (`🔮 inf.`, `**❓ open**`, `**⚠️ heavy**`, `**🛑 blocker**`) — a stray 🟥, 🚩, 🚨, ✅, or a naked emoji without its text label is wrong; **a Data-model table missing the `Source` column** (`client` / `inf.`); **a section that just says "X was not named" / "no automations named" / a "left out on purpose" list** — cut it, unknowns go in Open questions; **any bare `§N` reference** instead of a functional anchor link `[§N](#n-section-slug)`; **renumbering gaps** (e.g. cut a section but kept the old numbers around it); **a section that is mostly paragraphs where bullets or a table would do** — exception: Open questions stays a numbered list; **build / runtime / SDK mechanics that don't change the quote** (Docker version, OAuth flavour, auto-system relations, env-var names, CI/CD workflow detail); a point repeated across sections instead of a `[§N](...)` cross-reference; a leftover glossary / domain-language section; any capability claim stated as fact without a References source. Fix, then save to the lead folder as `YYYY-MM-DD-<lead>-design-doc.md`.
7. **Self-check, then save** — scan the output for: an em dash; a bare `~`; first-person voice outside customer quotes; local file paths; a header that isn't the four-field table; **any flag that isn't one of the three canonical emoji-and-text pairs** (`**❓ open**`, `**⚠️ heavy**`, `**🛑 blocker**`) — a stray 🟥, 🚩, 🚨, ✅, 🔮, or a naked emoji without its text label is wrong (in default mode `🔮 inf.` is a failure, not just a format issue; remove the whole inference); **a Data-model table that has a `Source` column** (default mode — remove it; only `--full` mode uses it); **a section that just says "X was not named" / "no automations named" / a "left out on purpose" list** — cut it, unknowns go in Open questions; **any bare `§N` reference** instead of a functional anchor link `[§N](#n-section-slug)`; **renumbering gaps** (e.g. cut a section but kept the old numbers around it); **a section that is mostly paragraphs where bullets or a table would do** — exception: Open questions stays a numbered list; **build / runtime / SDK mechanics that don't change the quote** (Docker version, OAuth flavour, auto-system relations, env-var names, CI/CD workflow detail); a point repeated across sections instead of a `[§N](...)` cross-reference; a leftover glossary / domain-language section; any capability claim stated as fact without a References source. Fix, then save to the lead folder as `YYYY-MM-DD-<lead>-partner-brief.md` (or `design-doc.md` in `--full` mode).
8. **Write partner-match-criteria.md** — always, as a third artifact alongside the brief. Save as `partner-match-criteria.md` in the same lead folder. Structure:
- **Hard requirements** — table: Criterion | Why. Include: language/region, deployment type, data model complexity signal, migration capability, engagement model (fixed vs retainer).
- **Strong preference** — bullets: domain familiarity, migration experience, willingness to scope for free, ability to work with non-technical end-users.
- **Nice to have** — bullets: industry-specific workflow experience, partner size fit.
- **Red flags** — bullets: anything that would disqualify a partner silently (language, hosting model, engagement type, technical depth).
- **What to send the partner** — numbered steps: which files to share, what to ask the partner to confirm, when to make the intro.
- **Matching notes** — 35 bullets for the partnerships team: technical nuances, prospect's sophistication level, migration unknowns, who the decision-makers are. Draw from the Open questions and Implementation complexity sections of the brief.
## Worked example
@@ -29,19 +29,18 @@ After the table, one **"What this is"** callout framing the doc as a partner-sco
**Flag legend (emoji + short text label, used together so they're scannable and unambiguous):**
- `🔮 inf.` modelling inference (yours; the partner should confirm with the client). Used inline, often, unbolded.
- **❓ open** open question to resolve before quoting.
- **⚠️ heavy** product-constrained or needs special design / has a real cost.
- **🛑 blocker** dealbreaker-grade. Doesn't quote without resolution.
Use these four pairings only. Don't invent new flags, swap the emoji (🟥 / 🚩 / 🚨 / ✅), or drop the text label and use the emoji alone.
Use these three pairings only by default. Don't invent new flags, swap the emoji (🟥 / 🚩 / 🚨 / ✅), or drop the text label and use the emoji alone. In `--full` mode a fourth flag is added: `🔮 inf.` — see Full mode section.
### Required sections (always present)
Number sequentially in the final doc, with no gaps:
- **Context**: the 30-second read: who they are, what they want, deployment requirement, scale, language/region.
- **Data model in Twenty terms**: the core. Present objects as a **table, one row per object**: `Object | Std/Custom | Source | Represents | Key fields | Core relations`. The **Source** column is `client` (object/concept grounded in client speech) or `inf.` (you coined it). Inline, tag inferred field names and relations `🔮 inf.`. Spell out SELECT option sets. **Model the relationships, not just the fields**: who introduced/sourced a record (e.g. an ambassador to Opportunity `sourcedBy` link), parent/child, ownership carry as much scoping signal as the attributes. State product constraints **only when they change the build or quote**, and inline (no formula fields → reporting ratios need a logic-function-maintained stored field; custom objects auto-get attachments/notes/tasks/timeline → relationship tracking is free). Where a customer term collides with a Twenty term (their "partner" = a donor), note the mapping inline as a small "term collisions" bullet list above the table; do **not** add a glossary section for it.
- **Data model in Twenty terms**: the core. Present objects as a **table, one row per object**: `Object | Std/Custom | Represents | Key fields | Core relations`. Include only objects the client explicitly named; include only fields the client explicitly named. If a cell has no grounded content, write `—`. Spell out SELECT option sets. **Model the relationships, not just the fields**: who introduced/sourced a record (e.g. an ambassador to Opportunity `sourcedBy` link), parent/child, ownership carry as much scoping signal as the attributes. State product constraints **only when they change the build or quote**, and inline (no formula fields → reporting ratios need a logic-function-maintained stored field; custom objects auto-get attachments/notes/tasks/timeline → relationship tracking is free). Where a customer term collides with a Twenty term (their "partner" = a donor), note the mapping inline as a small "term collisions" bullet list above the table; do **not** add a glossary section for it. In `--full` mode, add a `Source` column (`client` / `inf.`) and tag inferred field names and relations `🔮 inf.`.
- **Roles, permissions & RLS**: map named roles to Twenty's object / field / row-level model; answer "do we need RLS?" against verified, plan-gated capability.
- **Hosting & compliance**: cloud vs self-host, data-residency requirement (verify), GDPR. Flag contradictions.
- **Suggested phasing**: "(the partner's call, not Twenty's)" layers, labelled a suggestion.
@@ -61,12 +60,48 @@ Number whatever you include sequentially. A doc with Context, Data model, Integr
Scale each section to its content. **Coverage of surface area matters more than depth per item.**
## Default behavior: zero inference
The default output is a **zero-inference partner brief**: sharp, small, and strictly grounded in what the client said.
- **Nothing is inferred.** If the client didn't say it, it doesn't appear in the doc.
- **Required sections with no grounded content** get exactly one placeholder line: `> ⬜ Not discussed on call — needs input before this section can be filled.`
- **Conditional sections** (Automations, Integrations, Reporting, Views) with nothing grounded are **omitted entirely** — same rule as always; the placeholder applies only to required sections.
- **Data-model table cells** with no grounded content get `—`, not an invented value.
- **`🔮 inf.` never appears.** If you find yourself wanting to use it, that line should not exist in the doc.
- **Target length:** 1 page. Each section is 15 lines max.
- **Verification** is run only for sections that have grounded content — no need to verify empty sections.
- **Save as** `YYYY-MM-DD-<lead>-partner-brief.md`.
### Self-check (zero-inference additions)
After the standard self-check:
- `🔮 inf.` anywhere is a hard failure — remove the entire inference, not just the tag.
- Source column in the data-model table is a failure — remove it.
- Doc longer than 2 pages is a warning — cut until only grounded content remains.
## Full mode (`--full`)
Pass `--full` to produce a **full inference design doc** instead of the default zero-inference brief. Use when: discovery is complete, the lead is well-documented, and inferences are needed to give the partner a richer starting model.
| Dimension | Default (zero-inference) | `--full` |
|---|---|---|
| Inferences | Never | Allowed; tagged `🔮 inf.` |
| Empty section | Placeholder line | Fill with inferences + `🔮 inf.` tags |
| Data-model Source column | Omitted | `client` / `inf.` |
| Inferred field names / relations | Not included | Tagged `🔮 inf.` |
| Length | 1 page target | As long as content requires |
| Verification | Grounded sections only | All load-bearing claims |
| Filename | `YYYY-MM-DD-<lead>-partner-brief.md` | `YYYY-MM-DD-<lead>-design-doc.md` |
In `--full` mode the fourth canonical flag is active: `🔮 inf.` (modelling inference; yours; the partner should confirm with the client). Used inline, often, unbolded.
## Rules (and why each matters)
- **Be concise: maximum signal per word.** Say a lot in few words. Cut throat-clearing, scene-setting, hedges, and feature-tour prose; prefer a table or a tight clause to a paragraph. Length is not coverage: a short doc that names every requirement beats a long one that pads each. A hesitant buyer reads a focused doc; a bloated one reads as cost.
- **Bullets and tables over paragraphs, with one exception.** Default to bullets; reach for a table whenever rows share structure (objects, views, plans, paths). Use prose only for a nuance no bullet or table cell can carry. The Open questions section is the deliberate exception: always a numbered list, so the partner can read item 1, 2, 3 aloud.
- **Business decisions over technical mechanics.** Scope is what the partner *builds* and what the client *receives*: data sensitivity, who-sees-what, plan choice, hosting choice, integration surface, cost drivers. Cut SDK / runtime / build-tool internals that don't change the quote (Docker version, OAuth flavour, auto-system relations, env-var names, version-control workflow). The partner reads References for the docs that cover those.
- **Fact vs inference is the primary visibility split.** Plain prose = stated by the client. `🔮 inf.` tag inline = your modelling guess, every time it appears. The Data-model table carries a **Source** column (`client` / `inf.`). A partner skimming the doc must be able to see at a glance which lines they need to confirm with the client.
- **Grounded content only (default).** Every line in the doc must trace back to something the client said. If it wasn't stated, it doesn't appear — use the placeholder or `—` for empty cells. In `--full` mode, inferences are allowed and tagged `🔮 inf.`; the Data-model table gains a `Source` column (`client` / `inf.`) so the partner can see at a glance which lines to confirm with the client.
- **Section cross-references are functional anchor links.** Every `§N` reference must be a markdown anchor link: `[§N](#n-section-slug)`. The slug follows GitHub Flavored Markdown auto-anchoring (lowercase; spaces → hyphens; punctuation dropped; `&` removed leaving a double hyphen). A bare `§N` is unreadable to a partner skimming the doc, who just sees numbers with no way to jump.
- **No "left out" / "not named" placeholders.** The doc speaks only about content grounded in the source. A bullet that says *"sessions/programmes/schools left out on purpose"* or a section that says *"no reporting was named"* is filler: cut it. If you want to flag the absence, write it as a question in Open questions ("Are sessions/programmes first-class objects?") that gates a specific decision; otherwise, silence.
- **Never repeat yourself.** State each fact, constraint, or claim once, in its home section; elsewhere link to it (functional `[§N](...)`) rather than restate. Open questions and References are deliberate roll-ups: there, give the pointer and the decision the item gates, not a re-explanation of the body. Repetition is the main source of bloat, and two copies of a claim drift out of sync.
@@ -87,7 +122,7 @@ Scale each section to its content. **Coverage of surface area matters more than
- One line per paragraph: **no mid-sentence hard wraps** (they render as broken lines).
- **Never use em dashes (the long dash).** Restructure the sentence, or use a colon, comma, parentheses, or a period instead.
- **Never a bare `~`** for "approximately": GitHub markdown pairs `~...~` into strikethrough. Write "around" / "about".
- **Flags are the four emoji + text pairs only** (`🔮 inf.`, `**❓ open**`, `**⚠️ heavy**`, `**🛑 blocker**`). Don't swap the emoji or drop the text label.
- **Flags in default mode: three emoji + text pairs** (`**❓ open**`, `**⚠️ heavy**`, `**🛑 blocker**`). In `--full` mode a fourth is added: `🔮 inf.`. Don't swap the emoji or drop the text label.
- **Section cross-references are functional anchor links** (`[§N](#n-section-slug)`), never bare `§N`.
- Mark unverified capability claims `**❓ open**`, never as fact.
@@ -136,7 +171,7 @@ If a `.md` 404s, drop the suffix or re-derive from the docs index: the map can g
|---|---|
| "Twenty isn't a BI tool" / "can't do row-level" | Stale training. Twenty has Dashboards; row-level is on the Organization plan. **Verify live.** |
| Checked only the app-SDK doc for a product capability | Row-level lives in the product/pricing layer. **Verify the right layer.** |
| Added fields not in the source | Scope growth, wrong quote. Ground every field; tag inferences `🔮 inf.`. |
| Added fields not in the source | Scope growth, wrong quote. Only include what the client explicitly named; leave the cell `—` otherwise. |
| Made a human actor (e.g. ambassador) its own object by default | A human is a Person + role flag first; an object only if it needs its own pipeline/reporting. |
| Flagged an automation as "Workflow" | Prescribes the build, penalizes app-builders. Present both. |
| Flagged a limit with no fix | Dead-end flag. Pair every problem with a path. |
@@ -151,7 +186,7 @@ If a `.md` 404s, drop the suffix or re-derive from the docs index: the map can g
| Used an emoji other than the four canonical (🟥 / 🚩 / 🚨 / ✅), or used the emoji without the text label | Stick to `🔮 inf.`, `**❓ open**`, `**⚠️ heavy**`, `**🛑 blocker**`. The text label disambiguates. |
| Section is mostly paragraphs | Default to bullets and tables; prose only when a nuance can't fit a list. Exception: Open questions stays a numbered list. |
| Included build / runtime / SDK mechanics that don't move the quote | Wrong altitude. Business decisions, scope consequences, and References only; mechanics belong in the later technical phase. |
| Data-model table missing a Source column | Partner can't see at a glance which rows the client confirmed vs which are your inferences. Add `client` / `inf.`. |
| Data-model table has a Source column (default mode) | In the default zero-inference mode, the Source column is unnecessary noise — every row is `client`. Omit it; only add it in `--full` mode. |
| Cross-references are bare `§N` instead of functional links | The partner sees disconnected numbers and can't navigate. Use `[§N](#n-section-slug)` everywhere. |
| Included a section that just says "X was not named" / "no automations named" / a "left out on purpose" list | Cut the whole section / bullet. Unknowns go in Open questions; the doc speaks only about grounded content. |
| Renumbered with gaps (e.g. cut Reporting but kept §5-§11 numbering as §5, §6, §8) | Renumber sequentially, no gaps. A partner doesn't know which sections were omitted. |
@@ -0,0 +1,200 @@
---
name: twenty-partner-match
description: Match a qualified lead to Twenty partners and generate all intro emails in one run. Use when a lead folder exists and you need to find the right implementation partner, either after running twenty-partner-design-doc or standalone when partner-match-criteria.md is already present. Chains back into twenty-lead-intro-call-summary and twenty-partner-design-doc if the criteria file is missing.
trigger: /twenty-partner-match
---
# twenty-partner-match
Match a qualified lead to Twenty partners and generate all intro emails in one run.
Chains after `twenty-lead-intro-call-summary``twenty-partner-design-doc`. Can be run standalone if `partner-match-criteria.md` already exists in the lead folder.
---
## Credentials
Before running, `~/.twenty/credentials.env` must exist with:
```env
TWENTY_PARTNERS_API_URL=https://partners.twenty.com
TWENTY_PARTNERS_API_KEY=<your key>
# Only needed if the input is a Fireflies link/ID
FIREFLIES_API_KEY=<your key>
```
The partners API key lives in `packages/twenty-apps/internal/twenty-partners/.env.prod` (gitignored) — copy it to `~/.twenty/credentials.env` on first setup. The skill reads `credentials.env` at startup and stops cleanly if a required key is missing.
---
## Phase 0 — Prerequisites
### 0a. Credentials check
Read `~/.twenty/credentials.env`. Verify `TWENTY_PARTNERS_API_URL` and `TWENTY_PARTNERS_API_KEY` are present. If not, stop and tell the user exactly which key is missing and where to add it.
### 0b. Brief check
Look for `partner-match-criteria.md` in the lead folder.
**If missing:** Ask the user what is available — a text transcript, a Fireflies meeting ID/URL, or an existing call summary or brief. Then chain:
- Fireflies input → fetch transcript via Fireflies API (requires `FIREFLIES_API_KEY`) → `/twenty-lead-intro-call-summary``/twenty-partner-design-doc`
- Text transcript → `/twenty-lead-intro-call-summary``/twenty-partner-design-doc`
- Existing summary → `/twenty-partner-design-doc`
`partner-match-criteria.md` is produced by `/twenty-partner-design-doc` as Step 8.
**If present — be critical before proceeding.** Read the file and assess quality:
- Are there at least 2 hard requirements with justification?
- Is the language requirement explicit?
- Are the required skills specific enough to differentiate partners (e.g. "self-hosted Docker" beats "technical")?
- Are there red flags listed?
If the brief is thin on any of these axes, say so and ask targeted follow-up questions before querying the API. A match built on a vague brief is noise. Only proceed once the criteria are solid enough to produce a meaningful ranking.
---
## Phase 1 — Matching
### 1a. Fetch candidates
Query the partners API:
```graphql
query ListPartners($after: String) {
partners(
filter: {
validationStage: { eq: VALIDATED }
availability: { eq: AVAILABLE }
}
after: $after
) {
pageInfo {
hasNextPage
endCursor
}
edges {
node {
id name slug languagesSpoken skills deploymentExpertise
partnerScope partnerTier country region city introduction
persons { edges { node { name { firstName lastName } emails { primaryEmail } } } }
company { id name }
}
}
}
}
```
Paginate until `pageInfo.hasNextPage` is false, passing `pageInfo.endCursor` as `$after` each iteration. Endpoint: `$TWENTY_PARTNERS_API_URL/graphql`.
### 1b. Evaluate and rank
Evaluate every candidate against the criteria in `partner-match-criteria.md`. Apply hard requirements as eliminators first (a partner missing a hard requirement does not appear in results, even with a note). Then rank remaining candidates by fit across:
- **Language** (primary) — exact match on `languagesSpoken`
- **Skills and expertise** (primary) — semantic match: "ERP experience" is relevant to complex billing, "self-hosted Docker" maps to `SELF_HOST` deploymentExpertise, etc.
- **Country / region** (secondary) — proximity helps but is not a blocker unless the criteria say otherwise
### 1c. Present results
Show **at minimum 2 candidates**, more if others score well. For each:
```
## [Partner name] — [fort / moyen / faible]
**Pourquoi ça matche**
- point 1
- point 2
- point 3
**Ce qui manque ou est risqué**
- point
**Contacts** : [name, email if available]
```
Be honest. If only one candidate is a strong match, say so and explain why the others are weaker — don't artificially inflate scores to fill a quota.
### 1d. Pause for validation
Ask the user which partners to introduce. They can pick one, several, or none (and ask to search differently). Wait for explicit confirmation before generating emails.
---
## Phase 2 — Emails
For each confirmed partner, generate three email types. For N confirmed partners, the total is `1 + 2N` emails.
### Email rules (always applied)
- No em dashes (`—`) — use `:` or `,` instead
- Tutoiement by default
- Sign: `Cheers,\nRashad\nPartnerships @twenty`
- Subject prefix: `[Twenty]`
- No self-introduction for partner emails (they know who Rashad is)
- Open in Gmail via `https://mail.google.com/mail/?view=cm&fs=1&to=...&su=...&body=...` using `open -a "Google Chrome"`
### Email 1 — Client notification (one per run)
- **To**: client email
- **Subject**: `[Twenty] Ton projet CRM — intro(s) à venir`
- **Content**: who Rashad is + role at Twenty, brief project recap (2 sentences), announce incoming introductions by agency name(s)
### Email 2 per partner — Solo partner outreach
- **To**: partner contact email (from `persons``emails.primaryEmail`; leave blank if none found)
- **Subject**: `[Twenty] Opportunité partenaire : [Client], [one-line project description]`
- **Content**: why this opportunity matches their profile, project complexity (custom objects, deployment, migration, seats), invite them to respond or get on a call, brief to attach
### Email 3 per partner — Three-way intro
- **To**: partner contact email
- **CC**: client email
- **Subject**: `[Twenty] [Client] x [Partner] : Projet CRM`
- **Content**: introduce client to partner (one sentence each), project in two sentences, brief to attach, hand off
### Opening in Gmail
Open all emails sequentially with a 1.5s delay between each. Use Python:
```python
import subprocess, urllib.parse, time, sys
params = {"view": "cm", "fs": "1", "to": to, "su": subject, "body": body}
if cc: params["cc"] = cc
url = "https://mail.google.com/mail/?" + urllib.parse.urlencode(params)
if sys.platform == "darwin":
subprocess.run(["open", "-a", "Google Chrome", url])
else:
# Linux / Windows: fall back to the system default browser
import webbrowser
webbrowser.open(url)
time.sleep(1.5)
```
Remind the user to attach `YYYY-MM-DD-<lead>-partner-brief.md` to all partner emails before sending.
---
## Output files
Save email content as text files in the lead folder for reference:
- `email-1-client.txt`
- `email-2-<partner-slug>-solo.txt`
- `email-3-<partner-slug>-intro.txt`
Use the partner's `slug` field for filenames.
---
## Chain context
This skill is the third step in the Twenty partner pipeline:
```
/twenty-lead-intro-call-summary → /twenty-partner-design-doc → /twenty-partner-match
```
The `partner-match-criteria.md` produced by `/twenty-partner-design-doc` (Step 8) is this skill's primary input. Keep the criteria file updated as you learn more about the lead — it feeds both this skill and the eventual in-product matching logic.