Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

Back to skills

Sales Specialist

ASecurity

Business card pipeline — OCR extraction, schema normalization, Zynkr platform contact + company creation, company research, and personalized follow-up email drafting. Takes business-card IMAGES specifically — the OCR pass is the point. When the lead signal already arrives as text (a DM thread, an event feedback row, a website enquiry), use sales-outbound instead; for a WHOLE event survey sheet, use sales-client-sourcing.

20 stars
0 votes
0 copies
2 views
Added 9/19/2026
businessgobashsqlgit

Works with

climcp

Security Analysis

A100/100

Scanned 9/19/2026

Install to Claude Code

$npx -y skills add peter-tu-zynkr/zynkr-skill-builder --skill sales-specialist --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Sales Specialist?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Sales Specialist
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/peter-tu-zynkr-sales-specialist/badge)](https://www.skillsdirectory.com/skills/peter-tu-zynkr-sales-specialist)

More formats (shields.io, HTML) on the badges page.

Download Zip
Files
SKILL.md
---
name: sales-specialist
sheetId: "2.02"
description: "Business card pipeline — OCR extraction, schema normalization, Zynkr platform contact + company creation, company research, and personalized follow-up email drafting. Takes business-card IMAGES specifically — the OCR pass is the point. When the lead signal already arrives as text (a DM thread, an event feedback row, a website enquiry), use sales-outbound instead; for a WHOLE event survey sheet, use sales-client-sourcing."
category: sales-consultant
project: sales-specialist
platform: claude
status: Done
author: Peter Tu
input: "Business card image(s) — single or batch"
process: "OCR via Claude Vision → schema normalization → user review → Zynkr platform write (contact + company + 名片 note) → company research → follow-up email draft → Gmail save"
output: "Contact + company on the Zynkr platform (platform.zynkr.ai/contacts), a 名片 note holding the rest of the card, and a personalized follow-up email saved to Gmail Drafts"
synergy: []
type: agent
skills: ["sales-follow-up-mail"]
house-style: bound

---

# Sales Specialist

```bash
npx skills add https://github.com/peter-tu-zynkr/zynkr-skill-builder --skill sales-specialist
```

Automate the full post-trade-show workflow: extract contact info from business card images, land them on the Zynkr platform, research each company, and draft personalized follow-up emails. Use this skill after a conference, expo, or networking event where you've collected business cards and want them turned into working CRM records plus drafts ready to send before context fades.

**Not this skill when — the signal is already text.** What this skill owns is the OCR pass
from a photographed card. A DM thread, an event feedback row or a website enquiry goes to
`/sales-outbound`; a WHOLE event survey sheet goes to `/sales-client-sourcing`. Everything
downstream (CRM write, research, follow-up draft) is the same either way — only the parser
differs.

---

## Step 0 — Load Config

Read the config file at:
`./sales-specialist-config.md`

Read the contact schema at:
`./references/contact-schema.md`

Store:
- `EMAIL_SIGNATURE` — signature block from config
- `SCHEMA_FIELDS` — the 12-field list: name, title, company, email, phone, mobile, website, address, linkedin, industry, notes, card_date
- `CRM_PROJECT_ID` — Supabase project id from config (the Zynkr platform)
- `CONTACT_SQL` — the contents of `./references/contact-insert.sql` (read it now; you'll fill its placeholders per contact in Step 4)

**If `CRM_PROJECT_ID` is missing or `TBD`:**
```
⚠️  Zynkr platform project id not configured yet.
Set CRM_PROJECT_ID in sales-specialist-config.md before continuing.
```
Stop and ask the user — without it there is nowhere to write the cards.

---

## Step 1 — Card Intake

Display:
```
---------------------------------------------
📇 Business Card Pipeline
---------------------------------------------
Phase: Card Intake
---------------------------------------------
```

Ask the user:
```
Please share your business card image(s).
You can paste one image or multiple at once for batch processing.
```

Detect intake mode:
- **Single card** — 1 image → proceed to Step 2
- **Batch** — multiple images → process all in Step 2, then review all at once in Step 3

Store images as `CARD_IMAGES[]`.

---

## Step 2 — OCR & Extract

Display:
```
---------------------------------------------
Extracting contact info from card(s)...
---------------------------------------------
```

For each card image, use Claude Vision to extract all visible text and map it to the 12-field schema:

**Extraction rules:**
- Read every element on the card: name, title, company, logos, contact details, addresses, URLs, QR code domain (if visible), social handles
- Map each piece of data to the correct schema field
- If a field is not present on the card, set it to `—`
- For `industry`: infer from company name/website if not explicitly stated; use a short label (e.g., "SaaS", "製造業", "零售", "FinTech")
- For `card_date`: default to today's date unless user specifies otherwise
- If text is ambiguous (e.g., could be phone or fax), use field label context clues on the card

Store extracted data as `CONTACTS[]` — one object per card with all 12 fields.

---

## Step 3 — Schema Review

Display:
```
---------------------------------------------
Review Extracted Contact Info
---------------------------------------------
```

For each contact, display a clean table:

```
| Field      | Extracted Value |
|------------|-----------------|
| name       | [value]         |
| title      | [value]         |
| company    | [value]         |
| email      | [value]         |
| phone      | [value]         |
| mobile     | [value]         |
| website    | [value]         |
| address    | [value]         |
| linkedin   | [value]         |
| industry   | [value]         |
| notes      | [value]         |
| card_date  | [value]         |
```

For batch mode, display all contacts in a numbered list (`Contact 1 of N`, etc.).

Ask:
```
Does this look correct?
- Type [OK] to proceed
- Type [EDIT field=value] to correct a field (e.g., EDIT email=david@company.com)
- Type [ADD note=...] to append to the notes field
```

Accept corrections until user types `OK`. Update `CONTACTS[]` with confirmed data.

---

## Step 3.5 — Website Lookup (auto-fill if missing)

For each contact where `website` is `—` or blank:

Run a WebSearch query: `"[company name]" official website`

Parse the top result for the company's official domain. Apply these rules:
- Accept only the company's own domain (skip directories like twincn.com, iyp.com.tw, etc.)
- If a clear official site is found, set `website` to the full URL (e.g., `https://example.com`)
- If no clear official site found, leave `website` as `—`

Display the result inline (no separate confirmation step needed):
```
🌐 Website found: [URL] (auto-filled)
```
or:
```
🌐 Website: not found — left blank
```

Update `CONTACTS[]` with the resolved website before proceeding to the platform write.

---

## Step 4 — Zynkr Platform Write

Display:
```
---------------------------------------------
Writing to the Zynkr platform...
---------------------------------------------
```

The **Zynkr platform is the only destination** for a card. Everything lands at
`https://platform.zynkr.ai/contacts`, where the funnel is actually worked — there
is no spreadsheet copy to keep in sync, and no field is dropped on the way:

| Card field | Lands in |
|---|---|
| name | `crm_contacts.last_name` (whole, as printed) |
| title | `crm_contacts.title` |
| email | `crm_contacts.email` (the dedup key) |
| mobile | `crm_contacts.phone` — the number you'd actually use |
| company | `crm_companies.name` (find-or-create) |
| website | `crm_companies.domain` (scheme/`www.` stripped by the SQL) |
| industry | `crm_companies.industry` |
| address | `crm_companies.address` |
| phone (office) | `crm_companies.phone` — belongs to the company, not the person |
| linkedin · notes · card_date | a `名片` note on the contact (`crm_activities`) |

> Earlier versions of this skill also appended a row to a `B2B Customer List`
> Google Sheet, because `crm_contacts` had no home for 7 of the 12 fields. It has
> since grown `domain` / `industry` / `address` / `phone` on the company side, so
> the platform now holds the whole card and the Sheet write has been retired. The
> old Sheet stays readable as the historical record — just don't append to it.

Run this **autonomously** for every confirmed contact (no extra approval gate —
they already signed off in Step 3). For each contact in `CONTACTS[]`:

**1. Clean the values.** Translate the `—` sentinel (and any blank) to an empty
string for every field before using it. The platform should never store a literal `—`.

**2. Use the name as printed.** The contacts page renders a contact as
`last_name + first_name`, so to show the name exactly as it appears on the card,
put the **whole** name in `{{FULL_NAME}}` (it maps to `last_name`) and leave
`first_name` empty — don't split it into 姓/名. If the card prints both a Chinese
and a romanized name (e.g. `王大明 / David Wang`), use the Chinese name if present,
otherwise the romanized one — and store it whole (`王大明`, or `David Wang`).

**3. Keep both numbers.** Pass the mobile as `{{MOBILE}}` and the office line as
`{{PHONE}}` — they go to the contact and the company respectively, so neither is
lost. If the card shows only one number, pass it as `{{MOBILE}}` and leave
`{{PHONE}}` empty; the SQL falls back so the contact always has a number.

**4. Fill and run the SQL.** Take `CONTACT_SQL` (loaded in Step 0 from
`./references/contact-insert.sql`) and substitute the placeholders, **doubling any
single quote** (`O'Brien` → `O''Brien`):

| Placeholder      | Value |
|------------------|-------|
| `{{FULL_NAME}}`  | the name as printed (Chinese side if bilingual) |
| `{{EMAIL}}`      | `email` (empty string if the card had none) |
| `{{TITLE}}`      | `title` |
| `{{MOBILE}}`     | `mobile` (or the only number on the card) |
| `{{PHONE}}`      | `phone` — the office line (empty if the card has one number) |
| `{{COMPANY}}`    | `company` (empty string if blank) |
| `{{WEBSITE}}`    | `website` as printed — pass the full URL, the SQL normalizes it |
| `{{ADDRESS}}`    | `address` |
| `{{INDUSTRY}}`   | `industry` (inferred in Step 2 if not printed) |
| `{{LINKEDIN}}`   | `linkedin` |
| `{{NOTES}}`      | `notes` |
| `{{CARD_DATE}}`  | `card_date` (`YYYY-MM-DD`) |

Run it with `mcp__supabase__execute_sql`, `project_id` = `CRM_PROJECT_ID`. One
statement does all of it: find-or-create the company, backfill any company column
that is still empty (**never** overwriting a value a human curated), insert the
contact **only if the email isn't already on the platform** with the baked-in
defaults (`lifecycle_stage=lead`, `legal_basis=consent`, `lead_status=other`,
owner = Peter), and attach the 名片 note.

**5. Read the result and report.**
- One row back (a `contact_id`) → created. Display:
  ```
  ✓ [Contact Name] @ [Company] — added → https://platform.zynkr.ai/contacts/[contact_id]
  ```
- Zero rows back → a contact with this email already exists (de-duped). Display:
  ```
  ⏭️ [Contact Name] — already on the platform (matched by email), skipped
  ```
  Note this also means **no note was written** for that card — the note is attached
  to a newly created contact only, so a re-scan never stacks duplicate notes.

If the insert errors, surface it, keep the card in `CONTACTS[]` so it can be
retried at the end, and continue with the rest of the pipeline — the follow-up
email shouldn't be blocked by a platform hiccup. Because this is now the only
destination, an unretried failure means the card is **not recorded anywhere**: say
so explicitly in the Step 8 summary rather than reporting the contact as processed.

---

## Step 5 — Company Research

Display:
```
---------------------------------------------
Researching Company...
---------------------------------------------
[Company Name]
```

For each contact, run 3 WebSearch queries in sequence:

1. **Company overview**: `"[company name]" company overview products services`
2. **Recent news**: `"[company name]" news 2024 OR 2025`
3. **Industry context**: `[industry] Taiwan market trends 2025` (or adjust region based on company location)

Synthesize the search results into a `RESEARCH_SUMMARY` for each contact:

```
## Company: [Company Name]

### Overview
[2–3 sentences: what they do, business model, size/stage]

### Recent News
- [key item 1]
- [key item 2]

### Personalization Hook
[The single most relevant/interesting detail to reference in the follow-up email]
```

Display the research summary to the user. Ask:
```
Research looks good? [OK] or [EDIT] to add/change context.
```

---

## Step 6 — Follow-up Email Draft

Display:
```
---------------------------------------------
Drafting Follow-up Email...
---------------------------------------------
[Contact Name] @ [Company]
```

For each contact, launch the `followup-email-writer` agent using the Agent tool.

Pass the following in the prompt:
```
## Contact Info
[all 12 fields from CONTACTS[i]]

## Company Research Summary
[RESEARCH_SUMMARY for this contact]

## Meeting Context
[CONTACTS[i].notes — or "No specific notes" if empty]

## Language
[Detect from card: if Chinese characters on card → zh-TW; otherwise → en]

## Email Signature
[EMAIL_SIGNATURE from config]
```

Wait for the agent to complete. Display the drafted email.

Ask:
```
Email looks good?
- [OK] → save to Gmail Drafts
- [SEND] → send immediately
- [EDIT subject=...] / [EDIT body=...] → adjust the email
- [SKIP] → skip this contact's email
```

Accept edits until user approves.

---

## Step 7 — Save to Gmail Drafts / Send

For each approved email:

**If user chose [OK] (save as draft):**
Use `mcp__gmail__draft_email`:
- `to`: contact email from `CONTACTS[i]`
- `subject`: subject line from agent output
- `body`: email body from agent output
- `from`: `<your-google-workspace-account>`

**If user chose [SEND]:**
Use `mcp__gmail__send_email`:
- `to`: contact email from `CONTACTS[i]`
- `subject`: subject line from agent output
- `body`: email body from agent output
- `from`: `<your-google-workspace-account>`

Display confirmation per contact:
```
✓ [Contact Name] — email saved to Drafts / sent
```

---

## Step 8 — Pipeline Summary

Display final completion summary:

```
=============================================
📇 Business Card Pipeline — Complete
=============================================

Processed: [N] contact(s)

[Contact 1 Name] @ [Company]
  ✓ Added to the Zynkr platform (or ⏭️ already existed / ✗ FAILED — not recorded)
  ✓ 名片 note attached
  ✓ Email drafted / sent

[Contact 2 Name] @ [Company]
  ✓ Added to the Zynkr platform (or ⏭️ already existed / ✗ FAILED — not recorded)
  ✓ 名片 note attached
  ✓ Email saved to Drafts
  ...

Contacts: https://platform.zynkr.ai/contacts
=============================================
```

If any card shows `✗ FAILED`, list those cards again underneath and offer to retry
the platform write — a failed card exists nowhere but the image it came from.

---

## Progress Indicator

Show this line at the top of each phase:

```
📇 Intake → Extract → Review → Website → Platform → Research → Email → Done
```

Use `▶` for current phase, `✓` for completed, `○` for upcoming.

---

## Error Handling

- If OCR fails to extract a field: mark it `?` and flag it in the review table
- If the platform write fails: surface the error and continue the pipeline, but keep the card flagged. It is the only destination, so a failed card is recorded nowhere — always offer to retry at the end, and never report it as processed.
- If WebSearch returns no results: note "No recent news found" and proceed to email with overview only
- If Gmail draft/send fails: display the error; offer to copy email text to clipboard instead
- Never silently skip a step — always surface errors clearly

## House style

Writing style is **not owned by this file**. The house voice lives in two Google Docs under
`[@] 寫作指南` (`12DBdFz3SK22ie9im_ThFMI7IBRXsTZsV`), read at runtime:

- 《[2.0] Zynkr 通用風格指南 House Voice》 `10bOIQwRm9Pxwgct4hlwCwK_B4Pipai1HqBPZKzyRHSE` —
  the universal core, plus the addendum for this surface
- 《[3.2] 禁用詞清單 Forbidden Words》 `1N5sHLP4qzmmhpCGsi6KElxi1z0MFe4QZ0Q_35T10Uyg`

Read both before producing client- or reader-facing text, and scan the draft against 《[3.2]》
before handing it over. If Drive is unreachable, say so in the output rather than proceeding
unchecked. Never re-implement either list inside this file.

Attribution

peter-tu-zynkrpeter-tu-zynkr
View sourceMore from peter-tu-zynkr →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Solution Architect

Designs system architecture, component specifications, and technical integration strategy. Use when: designing solutions, system architecture, technology stack, or integration approaches.

192 votes

Akorchak:Venture Assessment

Generate a comprehensive VC investment assessment report for a company

72 votes

Stock Analysis

Analyze stocks and cryptocurrencies using Yahoo Finance data. Supports portfolio management (create, add, remove assets), crypto analysis (Top 20 by market cap), and periodic performance reports (daily/weekly/monthly/quarterly/yearly). 8 analysis dimensions for stocks, 3 for crypto. Use for stock analysis, portfolio tracking, earnings reactions, or crypto monitoring.

6511 votes

Just Fucking Cancel

Find and cancel unwanted subscriptions by analyzing bank transactions. Detects recurring charges, calculates annual waste, and helps you cancel with direct URLs and browser automation. Use when: 'cancel subscriptions', 'audit subscriptions', 'find recurring charges', 'what am I paying for', 'save money', 'subscription cleanup', 'stop wasting money'. Supports CSV import (Apple Card, Chase, Amex, Citi, Bank of America, Capital One, Mint, Copilot) OR Plaid API for automatic transaction pull. Out...

6511 votes

Telegram Compose

Compose rich, readable Telegram messages using HTML formatting via direct Telegram API. Use when: (1) Sending any Telegram message beyond a simple one-line reply, (2) Creating structured messages with sections, lists, or status updates, (3) Need formatting unavailable via Clawdbot's Markdown conversion (underline, spoilers, expandable blockquotes, user mentions by ID), (4) Sending alerts, reports, summaries, or notifications to Telegram, (5) Want professional, scannable message formatting wit...

6511 votes
View all in business →