360 Secure SkillHub
360 安全技能中心

mailgo-coldmail-marketing

Mailgo 冷邮件外联全流程技能:校验收件人邮箱、领取免费预热邮箱、生成并优化邮件主题与正文、创建/发送/暂停/删除活动、查看送达与回复报表。操作均通过内置 Python 脚本调用 Mailgo 接口,需先配置 MAILGO_API_KEY,并内置垃圾词扫描与占位符数据校验规则。

leadsnavideveloper
leadsnavideveloper
热度 40.40
最近更新 2026-05-11 17:45:06
分类:营销依赖:代码执行 / 读写文件 / 第三方 API / 内部调用大模型应用场景:邮件起草 / 文案创作 / 报告撰写 / 工作流 ...

安装技能

复制下面的话术发给你的 Agent,它会通过 secure-skills 检索并安装该技能。

执行命令安装此 skill: npx secure-skills-cli add mailgo-coldmail-marketing
Metadata
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.\n"}}
dependencies
{"python":">=3.7","optional":["openpyxl"]}

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: <script>, <style>, <meta>, <link>, <iframe>, <embed>, <object>
  • Remove: event handlers (onclick, onload, etc.)
  • Convert <style> blocks → inline styles, then remove block
  • Remove: HTML comments, CSS !important, base64 images
  • Ensure: all <img> have alt attributes

Step 3.4 — Structure Check

  • Text-to-image ratio > 60% text (warn if image-heavy)
  • Cold email target: < 150 words (flag if > 200)
  • One CTA only (suggest consolidation if multiple)
  • Placeholder Data Validation — scan all #{...} 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 unchanged

Step 3.5 — Link Audit

  • Replace URL shorteners (bit.ly, t.co, tinyurl, goo.gl, ow.ly) with full URLs
  • Warn if > 3 links in cold email
  • Remove javascript: and data: URLs

Step 3.6 — Final Polish

  • Add soft opt-out line if missing (see below)
  • Verify HTML is well-formed
  • Write to /tmp/email_optimized_<timestamp>.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 omit

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

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body style="margin:0; padding:20px; font-family:Arial, sans-serif; font-size:14px; line-height:1.6; color:#333333;">
  <div style="max-width:600px; margin:0 auto;">

    <!-- EMAIL CONTENT HERE -->

  </div>
</body>
</html>

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 <script> tag
Structure: 118 words (OK)
Links: 2 (OK)
Opt-out: Added
Subjects generated: 4 variants

Output: /tmp/email_optimized_1711468800.html
→ Use with: --body-file /tmp/email_optimized_1711468800.html

Severity Levels

Level Issues
Critical (must fix) <script> tags, shortener URLs, base64 images, javascript: URLs, "This is not spam", #{Company Name}/#{Domain}/#{Title} placeholders with no corresponding recipient data
Warning (recommend) Spam trigger words, image-heavy, >3 links, missing opt-out
Info Word count >150, multi-punctuation, subject line suggestions

Behavior Rules

  • Run automatically before every campaign send, even if not asked. Report "0 issues found" if clean.
  • If user says "don't change my content": scan and report only, do not modify.
  • Never change meaning. Replace words, don't rewrite paragraphs.
  • Placeholder rule differs by scenario:
    • Generating content from scratch: NEVER write a #{...} placeholder unless the corresponding recipient_fields value is true. Use the generic alternatives from the Placeholder Rules table instead.
    • Optimizing existing content: if the content already contains a #{...} placeholder with NO data available, MUST replace it with the generic alternative (Step 3.4 Placeholder Data Validation). Do not silently preserve it — an unresolved placeholder will appear as a blank gap to the recipient.
    • Exception: #{Name} and #{Email} are always safe — never remove or replace them.
  • NEVER use [bracketed placeholders] like [Name], [Company], [Title] in final email output. These must be replaced with actual sender data or omitted. They are template authoring aids, not send-time variables.
  • Always use actual sender info (name, title, company, website) in the email signature — collected from the user in the Prerequisites step.
  • Write HTML to /tmp/email_optimized_<timestamp>.html and use via --body-file.

Step 4 — Create & Send Campaign

Use the bundled script (MANDATORY)

source ~/.zshrc

# Option A: inline recipients
python3 scripts/run_campaign.py \
    --sender "claimed@mailbox.com" \
    --subject "Quick question about #{Company Name}" \
    --body-file /tmp/email_optimized_1711468800.html \
    --recipients "alice@example.com,bob@test.com" \
    --campaign-name "SaaS Outreach Mar 2026" \
    --daily-limit 50

# Option B: recipients from file (CSV/XLSX/TXT/JSON)
python3 scripts/run_campaign.py \
    --sender "claimed@mailbox.com" \
    --subject "Quick question about #{Company Name}" \
    --body-file /tmp/email_optimized_1711468800.html \
    --recipients-file leads.xlsx \
    --campaign-name "SaaS Outreach Mar 2026"

Script flags:

Flag Default Description
--sender required Sender email (from Step 2)
--subject required Subject line
--body — HTML body (or use --body-file)
--body-file — Read body from file (from Step 3)
--recipients — Comma-separated emails
--recipients-file — CSV/XLSX/TXT/JSON file
--email-column auto Override email column name
--name-column auto Override name column name
--company-column auto Override company column name
--title-column auto Override title/job column name
--domain-column auto Override domain/website column name
--recipient-names auto from email Comma-separated display names
--recipient-companies — Comma-separated company names
--recipient-titles — Comma-separated job titles
--recipient-domains — Comma-separated company websites
--campaign-name "Campaign" Display name
--timezone-id Asia/Singapore Timezone
--timezone-offset +08:00 UTC offset
--send-days 1,2,3,4,5 Days (1=Mon...7=Sun)
--send-hours 9,18 Start,end hour
--daily-limit 50 Daily send limit
--no-tracking off Disable open/click tracking
--dry-run off Create but do not activate

Output: JSON summary with campaignId, emailContentId, lead counts, activation status.

Agent Decision Framework

All campaign inputs were collected in Step 0.5. Use session_context directly.

From session_context (never ask again):

  • Recipients — session_context.recipients
  • Purpose/goal — session_context.campaign_purpose
  • Sender info — sender_company, sender_name, sender_title, sender_website, sender_offerings, sender_proof
  • Recipient field availability — recipient_fields.{name, company, title, domain}

Auto-inferred (never ask):

  • Sender email — auto-assigned from Step 2 (free mailbox)
  • Subject — generate from campaign_purpose (Step 3), using only placeholders with confirmed recipient_fields
  • Body — generate personalized HTML (Step 3), respecting recipient_fields availability
  • Campaign name — derived from campaign_purpose + current month (e.g. "Tire Outreach Mar 2026")
  • Schedule — Mon-Fri 9am-6pm, Asia/Singapore timezone
  • Daily limit — 50 for new accounts
  • Tracking — open + click ON

Decision Flow

0.5. [Already done] All info collected upfront — use session_context throughout
1.   Verify recipients (Step 1) if > 10 emails
2.   Claim mailbox (Step 2) if no sender specified
3.   Generate & optimize content (Step 3) — use ONLY placeholders with confirmed recipient_fields
4.   Run scripts/run_campaign.py (Step 4) — pass all available recipient fields
5.   Report: campaignId, name, lead count, activation status

API Reference (edge cases only)

Campaign creation is a 4-step sequential flow handled by the script:

Step 4a — Upload content:  POST /campaign/api/biz/mailgo/email/content/upload
Step 4b — Create campaign: POST /campaign/api/biz/mailgo/campaign/save
Step 4c — Add leads:       POST /campaign/api/biz/mailgo/leads/add
Step 4d — Activate:        POST /campaign/api/biz/mailgo/campaign/operate

Fall back to raw API only for: updating existing campaigns or adding leads from contact groups.


Step 5 — Manage Campaign Lifecycle

Use the bundled script (MANDATORY)

source ~/.zshrc

# Activate / Pause / Delete
python3 scripts/campaign_control.py activate <campaignId>
python3 scripts/campaign_control.py pause    <campaignId>
python3 scripts/campaign_control.py delete   <campaignId>   # confirm with user first!

# List campaigns
python3 scripts/campaign_control.py list                    # all
python3 scripts/campaign_control.py list --status 1         # active
python3 scripts/campaign_control.py list --status 0         # paused
python3 scripts/campaign_control.py list --name "outreach"  # fuzzy search

# Get campaign detail
python3 scripts/campaign_control.py info <campaignId>

Decision Flow

User says "stop/pause/resume/delete <campaign>"

1. Know campaign ID?
   YES → run script directly
   NO  → run: scripts/campaign_control.py list --status 1,0
         show results, ask user to pick one

2. If delete → confirm: "Delete campaign '{name}'? This cannot be undone."

3. Run script, report: "Campaign '{name}' (ID: {id}) is now {STATUS}."

Campaign Status Reference

Code Name Description
-1 DRAFT Not activated
0 PAUSE Paused
1 ACTIVE Sending
2 COMPLETE All leads processed
3 ERROR Error state

State transitions: DRAFT->ACTIVE, ACTIVE<->PAUSE, any->deleted

Abnormal States (abnormalType)

Code Meaning Action
1 Monthly quota exhausted Upgrade plan
2 Some sender accounts have issues Check account settings
4 All sender accounts have issues Fix sender accounts first
9 Daily sending limit reached Wait for next day
11 Email quota exhausted Upgrade plan

Step 6 — View Campaign Reports

Use the bundled script (MANDATORY)

source ~/.zshrc

# Overview stats
python3 scripts/campaign_report.py overview <campaignId>

# Per-round breakdown
python3 scripts/campaign_report.py rounds <campaignId>

# Daily progress (requires date range)
python3 scripts/campaign_report.py daily <campaignId> --start 2026-03-25 --end 2026-03-31

# List replies (metadata only)
python3 scripts/campaign_report.py replies <campaignId>

# List replies + read plain-text body of each reply
python3 scripts/campaign_report.py replies <campaignId> \
    --sender "sender@mailbox.com" \
    --read-content

# Raw JSON output
python3 scripts/campaign_report.py --json overview <campaignId>

Output Format

Always show rates, not raw numbers alone:

Campaign: "Q1 Outreach" (ID: 123456)  Status: ACTIVE
Leads: 500 total | 250 completed | 30 in progress | 200 not yet | 20 failed

  Sent:       450
  Delivered:  420  (93.3%)
  Opened:     180  (42.9%)
  Replied:     35  ( 8.3%)
  Clicked:     65  (15.5%)
  Bounced:     30  ( 6.7%)

Deliverability Health Metrics

Metric Healthy Critical
Open rate >20% <10%
Bounce rate <2% >5%
Spam complaints <0.1% >0.3%

API Reference (for advanced queries)

Report Endpoint
Overview POST /campaign/api/biz/mailgo/stat/overview
Per-round POST /campaign/api/biz/mailgo/stat/sequence-info
Send records POST /campaign/api/biz/mailgo/stat/sequence-detail
Reply list POST /campaign/api/biz/mailgo/stat/sequence-detail-reply
Reply content POST /tp/api/hmail/proxy — func: mbox:readMessage, mid: <messageId>
Reply detail POST /campaign/api/biz/mailgo/stat/sequence-detail-reply
Click detail POST /campaign/api/biz/mailgo/stat/sequence-detail-click
Contact history POST /campaign/api/biz/mailgo/stat/leads-send
Daily progress POST /campaign/api/biz/mailgo/stat/daily-step

Behavior Rules

  • Default to overview. Drill into details only on explicit request.
  • Always show rates alongside raw counts.
  • If sent == 0: say "No emails sent yet" — do not divide by zero.
  • If no campaign ID: list campaigns first, offer to drill into one.
  • If openClick == 0: tracking was disabled — say so.
  • If replied > 0 in overview: proactively suggest reading replies:
    💬 X reply(ies) detected. Run the following to read them:
    python3 scripts/campaign_report.py replies <campaignId> \
        --sender <senderEmail> --read-content
    

Global Error Handling

HTTP Action
401 Re-run Step 0 (token expired or invalid)
403 Ensure User-Agent header is set
429 Wait 60s, retry
500 Retry after 10s
502/503 Retry after 30-60s

Global Behavior Rules

  1. Script-first: Always use bundled scripts. Never reimplement API calls inline.
  2. Verify before send: Always run Step 1 for campaigns with >10 recipients.
  3. Optimize before send: Always run Step 3 before sending, even if not explicitly asked. Report "0 issues" if clean.
  4. Confirm destructive actions: Always confirm before delete. Campaign deletion is irreversible via API.
  5. Security: Never handle API keys in chat. User must create token in Personal Tokens and set as environment variable.
  6. Zero dependencies: All scripts use Python stdlib only (urllib, json, csv, ssl). No pip install required (except optional openpyxl for .xlsx files).