🎁 Get the FREE AI Skills Starter GuideSubscribe →
BytesAgainBytesAgain
🦀 ClawHub

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...

TERMINAL
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 ~/.zshrc

Windows 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 ~/.zshrc

Option A: inline emails

python3 scripts/verify_emails.py alice@example.com bob@gmail.com

Option B: from file (TXT/CSV/JSON)

python3 scripts/verify_emails.py --file leads.csv

Option 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

  • Never report results while UNCHECKED entries remain (unless timeout)
  • Show progress during polls: "Verifying... X/total processed"
  • Ask before filtering: "Found N undeliverable addresses. Remove them?"
  • Credit error = stop immediately
  • UNKNOWN is not a hard block — explain the tradeoff
  • API Details (edge cases only)

  • Submit: POST /sirius/api/biz/email/verification — body: {"emails": [...]}
  • Poll: GET /sirius/api/biz/email/verification/task/{taskId}
  • Max 10,000 emails per task; script auto-batches larger lists
  • 120 req/min rate limit; on 429 wait 60s

  • 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 ~/.zshrc

    Claim (idempotent — safe to run multiple times)

    python3 scripts/claim_free_mailbox.py

    JSON output for scripting

    python3 scripts/claim_free_mailbox.py --json

    What you get:

  • Pre-warmed mailbox with 90+ sender score
  • 60-day validity, no credit card required
  • Idempotent: re-running returns the same mailbox
  • stdout outputs just the email address (for piping)
  • > 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)

  • Endpoint: POST /api/biz/benefits/assign-prewarm
  • Auth: X-API-Key: {key}
  • Response: {"code": 0, "data": "email@domain.com"}
  • data: null = pool empty, contact support

  • Step 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:

  • ONLY use a #{...} placeholder if the corresponding data is confirmed available (either from the file columns or user-provided inline data).
  • If a field has NO data, NEVER use its #{...} placeholder. The placeholder will resolve to empty string at send time, creating ugly output like ,您好 or 关于 的合作.
  • Exception: #{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.
  • For other fields, use a generic alternative:
  • | 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

  • Plain text input → wrap in HTML skeleton (see below)
  • HTML input → preserve structure
  • Step 3.2 — Spam Trigger Scan

  • Scan subject + body against resources/spam-triggers.md
  • Replace each trigger with neutral alternative
  • Log every replacement
  • Key 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

  • Remove: