ManyMail API
The whole outreach flow as HTTP: register, add sender mailboxes, import contacts in bulk, broadcast, send one-off email, track progress, search replies. An AI agent can run it end to end; your own system can treat “send email” as a function call.
Base URL:https://api.hzhigzho.cn/api/v1
Every response is {"code":0,"message":"success","data":{…}}. code 0 means success.
Quick start
Five requests make one complete campaign. $API stands for the base URL above, $TOKEN for the token returned in step 1.
# 1) Register — returns a long-lived token
curl -X POST https://api.hzhigzho.cn/api/v1/agent/register \
-H 'Content-Type: application/json' \
-d '{"username":"my-ai-agent","password":"a-strong-password"}'
# 2) Add a sender: mailbox + app password, server guessed for you
curl -X POST https://api.hzhigzho.cn/api/v1/agent/senders \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"email":"you@gmail.com","auth_code":"app-password","from_name":"Alex"}'
# 3) Import contacts: tens of thousands at once, split every 800
curl -X POST https://api.hzhigzho.cn/api/v1/agent/contacts \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"emails":["a@example.com","b@example.com"],"group_size":800}'
# 4) Broadcast (starts immediately by default)
curl -X POST https://api.hzhigzho.cn/api/v1/agent/broadcast \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"subject":"Model A-1200 quote","content":"Hi, our quote is attached…","categories":["Group 1","Group 2"]}'
# 5) Check progress
curl https://api.hzhigzho.cn/api/v1/agent/tasks/<campaign_id> \
-H "Authorization: Bearer $TOKEN"
Authentication
Two credentials, same behaviour on every business endpoint:
- JWT — returned by register/login, sent as
Authorization: Bearer <token>. Long-lived; fine for a one-off integration. - API key — starts with
mmk_, revocable, usage-counted. Send asAuthorization: Bearer mmk_xxxorX-API-Key: mmk_xxx. Best when you hand access to an AI agent or a partner.
GET requests also accept ?token= so a browser link can carry the credential.
Account
| Field | Type | Notes |
|---|---|---|
username required | string | Email, phone, or letters/digits/underscore |
password required | string | At least 6 characters |
curl -X POST $API/agent/login -H 'Content-Type: application/json' \
-d '{"username":"my-ai-agent","password":"a-strong-password"}'
Returns sender count, contact total, category list, recent tasks and next_steps — plain instructions the model can follow directly.
{
"username": "my-ai-agent",
"max_senders": 1, // 0 = unlimited
"sender_count": 1,
"contact_total": 1600,
"categories": [{"id":"…","name":"Group 1"},{"id":"…","name":"Group 2"}],
"next_steps": ["Ready to send: POST /agent/broadcast …"]
}
Senders
You never need the SMTP host: QQ, 163, 126, Gmail, Outlook, iCloud, Alibaba and Tencent enterprise mail are built in. Custom domains are resolved through their MX record (Tencent / Alibaba / NetEase / Google / Microsoft), and only then guessed as smtp.domain.
No login required. Returns SMTP/IMAP host, port, provider name and a hint such as “use an app password”.
| Field | Type | Notes |
|---|---|---|
email required | string | Sender address |
auth_code required | string | App password / auth code (password is accepted too) |
from_name | string | Display name |
daily_limit | int | Per-mailbox daily cap, 0 = default |
smtp_host / smtp_port | string / int | Optional override when detection is wrong |
imap_enabled | bool | Sync incoming mail, default true |
{"code":0,"data":{"id":"…","smtp":"smtp.gmail.com:465","imap":"imap.gmail.com:993",
"detected_from":"preset","preset":{"provider":"Gmail","source":"preset"}}}
Contacts
One group holds 800 contacts by default. Import 1600 and you get “Group 1” and “Group 2”; pass category and they become “Customers-Group 1”, “Customers-Group 2”. Re-importing never duplicates — existing addresses are moved into the new group.
| Field | Type | Notes |
|---|---|---|
emails | string[] | Mailbox array; combine with or replace text |
text | string | Raw block separated by newline / comma / semicolon / space / tab |
category | string | Group name prefix; empty → Group 1, Group 2… |
group_size | int | Contacts per group, default 800, max 5000, -1 = no split |
{"input":1600,"inserted":1600,"updated":0,"skipped":0,
"groups":[{"category_id":"…","name":"Customers-Group 1","count":800},
{"category_id":"…","name":"Customers-Group 2","count":800}]}
Fuzzy match on email, name, company and phone. Returns trimmed records so the contact list never floods the model context.
Sending
| Field | Type | Notes |
|---|---|---|
to required | string | Comma separated for several recipients |
subject | string | Defaults to “Hello” |
content required | string | Plain text is converted to HTML with line breaks |
html | bool | Send content as-is |
signature | string | Appended signature |
from | string | Pick a mailbox; omitted → rotated pool |
| Field | Type | Notes |
|---|---|---|
subject required | string | Subject line |
content required | string | Body, plain text converted to HTML |
categories | string[] | Group names, e.g. ["Group 1","Group 2"] |
category_ids | string[] | Group ids instead of names |
emails | string[] | Raw addresses; imported into an “API quick send” group |
start | bool | Default true; false creates a draft only |
batch_size | int | 1 = one by one, >1 uses BCC, max 100 |
send_interval | int | Seconds between batches, 0 = global default |
html | bool | Body is HTML |
{"code":0,"data":{"campaign_id":"6aea…","status":"running","recipient_count":1600,
"next":"GET /agent/tasks/6aea… for sent / failed / opened / clicked"}}
"start": false, verify recipients and copy, then start it.
Progress
{"status":"running","recipient_count":1600,"progress":"42%",
"stats":{"total":1600,"sent":672,"success":669,"failed":3,"bounced":0,"opened":184,"clicked":37}}
status: draft, running, paused, completed, failed.
Search
Matches subject, sender and body. Add email to restrict to one counterpart. Bodies are truncated to 400 characters — enough to judge, small enough for a context window.
{"total":1,"items":[{"from":"buyer@example.com","subject":"RE: Model A-1200 quote",
"text":"Could you send a formal quote? We need 500 units…","received_at":"2026-11-04T02:30:04Z",
"is_read":false,"attachments":0}]}
API keys
curl -X POST $API/agent/keys -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' -d '{"name":"my-agent"}'
# plaintext is returned exactly once
{"id":"…","prefix":"mmk_c405929c","api_key":"mmk_c40592…"}
Errors & limits
| code | Meaning | What to do |
|---|---|---|
0 | Success | — |
400 | Bad request | Read message; it names the missing field |
401 | No or invalid credential | Log in again or rotate the API key |
403 | Account banned | Contact support |
404 | Not found | Make sure the id belongs to this account |
500 | Server error | Retry once; contact us if it persists |
- Sender count is quota-limited (1 by default). Need more? Write to mark@highzo.com.
- Each mailbox can carry its own
daily_limit; rotation spreads load and lowers spam/ban risk. - 800 contacts per group by default; keep a single import under ~200k addresses.
Notes for AI agents
Hand a model these two URLs and it has everything:
https://www.hzhigzho.cn/docs/openapi.json— machine-readable schemahttps://www.hzhigzho.cn/llms.txt— plain-text product and API summary
Recommended order — every step is idempotent and safe to retry:
GET /agent/me // inspect, fill gaps
POST /agent/senders // mailbox + app password is enough
POST /agent/contacts // tens of thousands, auto grouped
POST /agent/broadcast {"start":false} // draft first
POST /agent/tasks/:id/start // then actually send
GET /agent/tasks/:id // poll progress
GET /agent/messages/search // read replies, follow up