mailgo-coldmail-marketing
by @leadsnavideveloper
Complete cold email campaign suite for Mailgo — verify recipients, claim free mailbox, generate & optimize content, create campaigns, manage lifecycle, and v...
clawhub install mailgo-coldmail-marketing📖 About This Skill
name: mailgo-campaign-suite description: Complete cold email campaign suite for Mailgo — verify recipients, claim free mailbox, generate & optimize content, create campaigns, manage lifecycle, and view reports. All-in-one skill that handles the full outreach pipeline end-to-end. Use when a user wants to send cold emails, launch outreach campaigns, or manage existing campaigns. env: MAILGO_API_KEY: required: true description: > Mailgo OpenAPI Key. Obtain from https://app.mailgo.ai → Click avatar in bottom-left corner → Personal Tokens → Create Token. Used as X-API-Key header for all Mailgo API calls (api.leadsnavi.com). Never paste into chat — set as local env var only. dependencies: python: ">=3.7" optional: - openpyxl # for .xlsx file support
Mailgo Campaign Suite
One skill, complete cold email pipeline. From recipient verification to campaign reporting — everything runs through bundled scripts with zero third-party dependencies.
Step 0 — Authentication Setup
Required: MAILGO_API_KEY environment variable must be set before any other step.
Quick check
# Confirm the variable is set (shows first 5 characters only)
echo "${MAILGO_API_KEY:0:5}"
If output is non-empty → proceed to Step 1. If empty → follow the setup flow below.
Setup Flow
Sub-step 0.1 — Register or Log In
New users: 1. Go to https://app.mailgo.ai 2. Click "Sign Up" and complete registration
Existing users: 1. Go to https://app.mailgo.ai 2. Log in with your credentials
Sub-step 0.2 — Create Personal Token
Once logged in: 1. Click your avatar in the bottom-left corner 2. Select Personal Tokens from the menu 3. Click Create Token 4. Give your token a descriptive name (e.g., "Claude Code") 5. Copy the generated token
> SECURITY: Never paste the token into chat. It must only be set as a local environment variable.
Sub-step 0.3 — Set Environment Variable
# macOS / Linux (permanent)
echo 'export MAILGO_API_KEY="YOUR_TOKEN"' >> ~/.zshrc && source ~/.zshrcWindows PowerShell (permanent)
[System.Environment]::SetEnvironmentVariable('MAILGO_API_KEY', 'YOUR_TOKEN', 'User')
Replace YOUR_TOKEN with the copied value.
Sub-step 0.4 — Verify
# macOS / Linux — shows first 5 characters to confirm without exposing the token
echo "${MAILGO_API_KEY:0:5}"Windows PowerShell
$k = $env:MAILGO_API_KEY; if ($k) { $k.Substring(0, [Math]::Min(5, $k.Length)) } else { "" }
If output is non-empty, you're ready. If empty, run source ~/.zshrc or open a new terminal.
Key Facts
| Item | Value |
|------|-------|
| Token source | app.mailgo.ai → Avatar → Personal Tokens |
| Env variable name | MAILGO_API_KEY |
| API header | X-API-Key: {MAILGO_API_KEY} |
| Token scope | Your Mailgo account (mailboxes, campaigns, reports) |
| Revoke token | app.mailgo.ai → Avatar → Personal Tokens → Delete |
Auth Error Handling
| Issue | Fix |
|-------|-----|
| Empty after source ~/.zshrc | Open a new terminal window |
| 401 Unauthorized | Token may be invalid or deleted — create a new one in Personal Tokens |
| 403 Forbidden | Ensure User-Agent header is set (scripts handle this automatically) |
Step 0.5 — Upfront Information Gathering
Collect all required information in a single interaction before starting the pipeline.
Ask all questions at once. Do not ask in separate rounds. The user fills in what they know; mark anything skipped as "not provided".
> When to run: Only when the user intends to create a new campaign. Skip for report/manage/replies tasks.
Group A — Your Information (for email signature & credibility)
Ask all of these together in one message:
| # | Question | Field | Required? |
|---|----------|-------|-----------|
| 1 | What is your company name? | sender_company | Yes |
| 2 | What is your name (for the signature)? | sender_name | Yes |
| 3 | What does your company offer (products / services)? | sender_offerings | Yes |
| 4 | What is your job title? | sender_title | Recommended |
| 5 | What is your company website? | sender_website | Recommended |
| 6 | Any notable clients, certifications, or metrics to mention? | sender_proof | Optional |
Group B — Your Recipients
Ask all of these together in the same message as Group A:
| # | Question | Field |
|---|----------|-------|
| 7 | Who are you sending to? Paste emails, or provide a file path (CSV / XLSX / TXT). | recipients |
| 8 | What is the goal of this campaign? (one sentence) | campaign_purpose |
| 9 | Do your recipients have names available? (column name or yes/no) | has_name |
| 10 | Do you have recipients' company names? (column name or yes/no) | has_company |
| 11 | Do you have recipients' job titles? (column name or yes/no) | has_title |
| 12 | Do you have recipients' company websites / domains? (column name or yes/no) | has_domain |
> If the user provides a file, auto-detect columns after reading the file headers — then mark fields 9–12 automatically without asking again.
>
> ⚠ Header Warning: Column detection relies entirely on header names. Files without headers (e.g. a bare two-column XLSX with email + company but no row of column names) will only have col_0, col_1, … as synthetic headers — none of which match any known field name. As a result, only the email column can be identified by content scanning; all other columns (company, name, title, domain) will be silently discarded.
> Whenever a user provides a headerless file and you can see that extra columns exist (e.g. "two-column spreadsheet"), proactively warn them:
> _"Your file appears to have no header row. Only email addresses were imported — company names and other fields were not recognized. Add a header row (e.g. email, company, name) and re-upload to include that data."_
Auto-Derive Name from Email Prefix (CRITICAL)
Immediately after receiving the recipient list, check whether name data is available.
If has_name is false (no name column detected, no names provided inline):
1. Extract name from each email's local-part (the portion before @):
- Split on ., -, _ → capitalize each segment → join with space
- Examples: alice.smith@co.com → "Alice Smith", john_doe@co.com → "John Doe", jdoe@co.com → "Jdoe"
2. Store the derived name into each recipient entry in session_context.recipients
3. Set recipient_fields.name = true with source noted as "auto-derived from email prefix"
4. Do this silently — do not ask the user, do not announce it; just note it in the Step 3 change summary
This ensures #{Name} is always available as long as there are valid email addresses.
Handling Partial Answers
| Situation | Action |
|-----------|--------|
| User skips a required field (1, 2, 3, 7, 8) | Ask once more specifically for that field only |
| User skips a recommended field (4, 5) | Mark as not provided, do not ask again |
| User skips an optional field (6) | Mark as not provided, move on |
| User says "I'll send to a file I haven't uploaded yet" | Ask for file path, then detect columns |
| User provides inline recipients with no extra data | Auto-derive names from email prefixes (see above); mark fields 10–12 as not provided |
| User provides a file without a header row and extra columns are present | Warn: only emails were imported; other columns were discarded. Ask user to add a header row (email, company, name, etc.) and re-upload |
Carry Forward
Store all answers as session context. Every subsequent step uses this context directly — never ask for the same information again.
session_context = {
sender_company: "...", # or null
sender_name: "...", # or null
sender_title: "...", # or null
sender_website: "...", # or null
sender_offerings: "...", # or null
sender_proof: "...", # or null
recipients: [ # each entry always has a name (user-provided or auto-derived)
{ email: "...", name: "...", company: "...", title: "...", domain: "..." },
...
],
campaign_purpose: "...",
recipient_fields: {
name: true, # ALWAYS true — either user-provided or auto-derived from email prefix
company: true/false,
title: true/false,
domain: true/false,
}
}
Step 1 — Verify Recipient Emails
Always verify before sending to >10 recipients.
Use the bundled script (MANDATORY)
source ~/.zshrcOption A: inline emails
python3 scripts/verify_emails.py alice@example.com bob@gmail.comOption B: from file (TXT/CSV/JSON)
python3 scripts/verify_emails.py --file leads.csvOption C: override column detection
python3 scripts/verify_emails.py --file leads.csv --email-column "Email Address"
Script flags:
| Flag | Default | Description |
|------|---------|-------------|
| emails | — | Space-separated email addresses |
| --file | — | Read from TXT/CSV/JSON file |
| --email-column | auto-detect | Override email column name |
| --timeout | 180 | Poll timeout in seconds |
| --interval | 5 | Poll interval in seconds |
Output: JSON on stdout with categorized results:
{
"total": 5,
"valid": [{"email": "...", "status": "VALID"}],
"invalid": [...],
"domain_error": [...],
"unknown": [...],
"unchecked": [...]
}
Email Status Values
| Status | Meaning | Recommendation |
|--------|---------|----------------|
| VALID | Confirmed deliverable | Send |
| UNKNOWN | Catch-all or greylisting | Send with caution |
| DOMAIN_ERROR | No MX records | Do not send |
| INVALID | Malformed or SMTP-rejected | Do not send |
| UNCHECKED | Still processing | Wait or re-poll |
Filtering guidance: Conservative = VALID only. Aggressive = VALID + UNKNOWN.
Behavior Rules
UNCHECKED entries remain (unless timeout)API Details (edge cases only)
POST /sirius/api/biz/email/verification — body: {"emails": [...]}GET /sirius/api/biz/email/verification/task/{taskId}Step 2 — Claim Free Pre-Warmed Mailbox
Run before creating any campaign. Each user gets one free mailbox (60 days, 90+ sender score).
Use the bundled script (MANDATORY)
source ~/.zshrcClaim (idempotent — safe to run multiple times)
python3 scripts/claim_free_mailbox.pyJSON output for scripting
python3 scripts/claim_free_mailbox.py --json
What you get:
> Note: The free mailbox uses a randomly assigned domain (e.g. you@randomdomain.io) which may not match your company domain. If brand consistency matters, consider purchasing a dedicated domain at https://app.mailgo.ai.
Auto-integration
After claiming, use the returned email as--sender in Step 4:
SENDER=$(python3 scripts/claim_free_mailbox.py)
python3 scripts/run_campaign.py --sender "$SENDER" ...
API Details (edge cases only)
POST /api/biz/benefits/assign-prewarmX-API-Key: {key}{"code": 0, "data": "email@domain.com"}data: null = pool empty, contact supportStep 3 — Generate & Optimize Email Content
This is a knowledge-and-rewrite step. No API calls. No scripts.
Apply the 6-step optimization pipeline to user content, or generate from scratch.
Prerequisites for Generation — Use Session Context (CRITICAL)
All sender and recipient information was collected in Step 0.5. Do NOT ask again.
Read directly from session_context:
| Context field | Use in email |
|---------------|--------------|
| sender_name | Signature name |
| sender_title | Signature title (omit if null) |
| sender_company | Signature company + body references |
| sender_website | Signature website (omit if null) |
| sender_offerings | Value bridge paragraph |
| sender_proof | Proof point sentence (omit if null) |
| recipient_fields.name | Always true — use #{Name} unconditionally (user-provided or auto-derived from email prefix in Step 0.5) |
| recipient_fields.company | Use #{Company Name} only if true |
| recipient_fields.title | Use #{Title} only if true |
| recipient_fields.domain | Use #{Domain} only if true |
> If session_context is missing (e.g. user jumped directly to Step 3), collect the missing fields inline — but only the ones not yet known.
Template Variables and Placeholder Rules (CRITICAL)
Mailgo uses #{...} placeholders that are resolved server-side at send time:
| Placeholder | Description | Resolved from |
|-------------|-------------|---------------|
| #{Name} | Recipient's name | contactName in leads data |
| #{Company Name} | Company name | companyName in leads data |
| #{Domain} | Company website | domain in leads data |
| #{Title} | Job title | title in leads data |
| #{Email} | Email address | contactEmail in leads data |
CRITICAL — Conditional Placeholder Rule:
#{...} placeholder if the corresponding data is confirmed available (either from the file columns or user-provided inline data).#{...} placeholder. The placeholder will resolve to empty string at send time, creating ugly output like ,您好 or 关于 的合作.#{Name} is ALWAYS available. Every recipient has a name — either provided by the user or auto-derived from the email prefix in Step 0.5. Never fall back to "您好" when you have email addresses.| Placeholder | If data available | If data NOT available |
|-------------|-------------------|----------------------|
| #{Name} | #{Name},您好 | _(never happens — always use #{Name})_ |
| #{Company Name} | 关于 #{Company Name} 的合作 | 关于贵公司的合作 / About a potential partnership |
| #{Domain} | 我浏览了 #{Domain} | Omit this sentence entirely |
| #{Title} | 作为 #{Title} | Omit or use generic phrasing |
Log placeholder decisions in the change summary:
Placeholders used: #{Name} (data available), #{Email} (always available)
Placeholders skipped: #{Company Name} (no data), #{Title} (no data), #{Domain} (no data)
Optimization Pipeline (execute ALL 6 steps, in order)
Step 3.1 — Normalize
Step 3.2 — Spam Trigger Scan
resources/spam-triggers.mdKey replacements (most common):
| Trigger | Replacement |
|---------|-------------|
| free / free trial | included / trial period |
| Click here | Learn more / See details |
| Act now | When you're ready |
| Limited time | While available |
| Guaranteed | Proven |
| Dear friend | Hi #{Name} |
| ALL CAPS words | Sentence case |
| !!! / ??? | Single punctuation |
Full replacement table: resources/spam-triggers.md
Step 3.3 — HTML Cleanup
, , , , , , onclick, onload, etc.) blocks → inline styles, then remove block!important, base64 images![]()
have alt attributesStep 3.4 — Structure Check
#{...} in the content against session_context.recipient_fields:#{Name} and #{Email} → always safe, never touch
- #{Company Name} → if recipient_fields.company == false: replace with generic text (e.g. 贵公司 / your company); log the replacement
- #{Domain} → if recipient_fields.domain == false: remove the sentence containing it entirely; log the removal
- #{Title} → if recipient_fields.title == false: replace with generic phrasing or omit; log the replacement
- If data IS available for a placeholder: preserve it unchangedStep 3.5 — Link Audit
javascript: and data: URLsStep 3.6 — Final Polish
/tmp/email_optimized_.html Content Generation (from scratch)
Generate 4 subject line variants for A/B testing. Adapt based on available data:
If #{Company Name} data IS available:
| # | Strategy | Example |
|---|----------|---------|
| 1 | Value proposition | How #{Company Name} can cut onboarding time by 60% |
| 2 | Industry insight | A shift I'm seeing in #{Company Name}'s space |
| 3 | Case study / metrics | How [similar company] grew pipeline 3x in 90 days |
| 4 | Curiosity / question | Quick question about #{Company Name}'s [process] |
If #{Company Name} data is NOT available:
| # | Strategy | Example |
|---|----------|---------|
| 1 | Value proposition | Cutting onboarding time by 60% — here's how |
| 2 | Industry insight | A shift I'm seeing in the [industry] space |
| 3 | Case study / metrics | How [similar company] grew pipeline 3x in 90 days |
| 4 | Curiosity / question | A quick question about your [process] |
Subject rules: 40-50 chars, always personalize with #{Name} (always available); use #{Company Name} only if data available. No ALL CAPS, no spam triggers, no fake Re:/Fwd:.
Body structure (adapt based on available fields):
1. Personal opener (1 sentence)
- ALWAYS use "#{Name},您好:" — #{Name} is always available (user-provided or auto-derived)
- WITH #{Company Name}: reference it in opener
- WITHOUT #{Company Name}: use generic "贵公司" / "your company" or omit2. Value bridge (1-2 sentences) — connect their situation to your offering
3. Proof point (1 sentence) — specific metric or case study (use sender_proof if available)
4. Soft CTA (1 sentence) — question, not demand
5. Signature — use actual sender_name + sender_title + sender_company (NEVER use placeholders like [Name])
6. Soft opt-out
Body rules: under 150 words, conversational tone, one CTA. All sender info in signature must be real data, not placeholders.
Select industry template from resources/industry-templates.md based on user's offerings.
HTML Skeleton
Use for plain-text wrapping or building from scratch:
Soft Opt-Out Line
Always add after signature if missing:
P.S. If this isn't relevant, just reply and let me know — I won't reach out again.
Change Summary Format
Email Optimization Complete
Industry template: SaaS / Software (matched from "AI deployment platform")
Sender info: Zhang Wei, Sales Director, TirePro Technologies (www.tirepro.com)
Recipient data available: name (auto-derived from email prefix), company (no), title (no), domain (no)
Placeholders used: #{Name} (auto-derived from email prefix — e.g. alice.smith@co.com → "Alice Smith")
Placeholders skipped: #{Company Name}, #{Title}, #{Domain} (no data — generic wording used)
Spam triggers replaced: 3
- "free trial" → "trial period"
- "Click here" → "Learn more"
- "Act now" → "When you're ready"
HTML issues fixed: 1
- Removed