Quickstarts
Six flows, four ways to call them.
curl, Python, TypeScript, and MCP are all in this page. Markdown: /docs/quickstarts.md.
Create an inbox
POST /api/v1/onboard creates an account, a Bearer key, and username@cooperemail.com. The api_key is returned once.
curl -s https://cooperemail.com/api/v1/onboard \
-H 'content-type: application/json' \
-d '{"username":"research-bot"}'import json, urllib.request
req = urllib.request.Request(
"https://cooperemail.com/api/v1/onboard",
data=json.dumps({"username": "research-bot"}).encode(),
headers={"content-type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req) as res:
body = json.load(res)
print(body["api_key"], body["inbox"]["email"], body["inbox"]["id"])const response = await fetch("https://cooperemail.com/api/v1/onboard", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ username: "research-bot" }),
});
const created = await response.json();
const key = created.api_key as string;
const inboxId = created.inbox.id as string;
const email = created.inbox.email as string;{
"tool": "cooper_onboard",
"arguments": { "username": "research-bot" }
}Send an email
POST /api/v1/inboxes/{id}/messages sends from the inbox. client_id and Idempotency-Key stop a retry from sending twice.
curl -s https://cooperemail.com/api/v1/inboxes/$INBOX/messages \
-H "authorization: Bearer $COOP_KEY" \
-H "content-type: application/json" \
-H "idempotency-key: send-001" \
-d '{"to":["ada@example.com"],"subject":"Hello","text":"Tuesday works.","client_id":"send-001"}'import json, os, urllib.request
req = urllib.request.Request(
f"https://cooperemail.com/api/v1/inboxes/{os.environ['INBOX']}/messages",
data=json.dumps({
"to": ["ada@example.com"],
"subject": "Hello",
"text": "Tuesday works.",
"client_id": "send-001",
}).encode(),
headers={
"authorization": f"Bearer {os.environ['COOP_KEY']}",
"content-type": "application/json",
"idempotency-key": "send-001",
},
method="POST",
)
with urllib.request.urlopen(req) as res:
print(json.load(res)["id"])await fetch(`https://cooperemail.com/api/v1/inboxes/${inboxId}/messages`, {
method: "POST",
headers: {
authorization: `Bearer ${key}`,
"content-type": "application/json",
"idempotency-key": "send-001",
},
body: JSON.stringify({
to: ["ada@example.com"],
subject: "Hello",
text: "Tuesday works.",
client_id: "send-001",
}),
});{
"tool": "cooper_send_message",
"arguments": {
"inbox_id": "research-bot",
"to": ["ada@example.com"],
"subject": "Hello",
"text": "Tuesday works.",
"client_id": "send-001",
"idempotency_key": "send-001"
}
}Wait for a verification code
GET /api/v1/inboxes/{id}/wait-for-code holds up to 55 seconds and returns the code and message id. The body is not returned.
curl -s "https://cooperemail.com/api/v1/inboxes/$INBOX/wait-for-code?wait=25" \
-H "authorization: Bearer $COOP_KEY"import json, os, urllib.request
url = f"https://cooperemail.com/api/v1/inboxes/{os.environ['INBOX']}/wait-for-code?wait=25"
req = urllib.request.Request(url, headers={"authorization": f"Bearer {os.environ['COOP_KEY']}"})
with urllib.request.urlopen(req) as res:
body = json.load(res)
print(body["code"], body["message"]["id"])const waiting = await fetch(
`https://cooperemail.com/api/v1/inboxes/${inboxId}/wait-for-code?wait=25`,
{ headers: { authorization: `Bearer ${key}` } },
);
const code = await waiting.json();
console.log(code.code, code.message.id);{
"tool": "cooper_wait_for_code",
"arguments": { "inbox_id": "research-bot", "wait": 25 }
}Register a webhook
POST /api/v1/webhooks registers an HTTPS URL. Verify x-cooper-signature-v2 over the timestamp, a dot, and the raw body, and dedupe on x-cooper-delivery.
curl -s https://cooperemail.com/api/v1/webhooks \
-H "authorization: Bearer $COOP_KEY" \
-H "content-type: application/json" \
-d '{"url":"https://example.com/hooks/cooper","events":["message.received"],"client_id":"hook-1"}'import hashlib, hmac, json, os, urllib.request
req = urllib.request.Request(
"https://cooperemail.com/api/v1/webhooks",
data=json.dumps({
"url": "https://example.com/hooks/cooper",
"events": ["message.received"],
"client_id": "hook-1",
}).encode(),
headers={
"authorization": f"Bearer {os.environ['COOP_KEY']}",
"content-type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(req) as res:
secret = json.load(res)["secret"]
def verify(secret, raw_body, timestamp, signature):
expected = "v1=" + hmac.new(
secret.encode(), f"{timestamp}.{raw_body}".encode(), hashlib.sha256
).hexdigest()
return any(hmac.compare_digest(expected, part.strip()) for part in signature.split(","))await fetch("https://cooperemail.com/api/v1/webhooks", {
method: "POST",
headers: {
authorization: `Bearer ${key}`,
"content-type": "application/json",
},
body: JSON.stringify({
url: "https://example.com/hooks/cooper",
events: ["message.received"],
client_id: "hook-1",
}),
});{
"tool": "cooper_register_webhook",
"arguments": {
"url": "https://example.com/hooks/cooper",
"events": ["message.received"]
}
}Buy a phone number and read an inbound text
POST /api/v1/inboxes/{id}/phone-number buys a US number for a Starter or Pro inbox that has a verified owner. Inbound texts are stored on the inbox. cooper_wait_for_sms waits for a text. GET events?types=message.received&wait=25 waits for the next received message on that inbox.
curl -s https://cooperemail.com/api/v1/inboxes/$INBOX/phone-number \
-H "authorization: Bearer $COOP_KEY" \
-H "content-type: application/json" \
-H "idempotency-key: phone-1" \
-d '{"area_code":"415"}'
curl -s "https://cooperemail.com/api/v1/inboxes/$INBOX/events?types=message.received&wait=25" \
-H "authorization: Bearer $COOP_KEY"import json, os, urllib.request
inbox = os.environ["INBOX"]
headers = {
"authorization": f"Bearer {os.environ['COOP_KEY']}",
"content-type": "application/json",
"idempotency-key": "phone-1",
}
buy = urllib.request.Request(
f"https://cooperemail.com/api/v1/inboxes/{inbox}/phone-number",
data=json.dumps({"area_code": "415"}).encode(),
headers=headers,
method="POST",
)
with urllib.request.urlopen(buy) as res:
print(json.load(res)["e164"])
wait = urllib.request.Request(
f"https://cooperemail.com/api/v1/inboxes/{inbox}/events?types=message.received&wait=25",
headers={"authorization": headers["authorization"]},
)
with urllib.request.urlopen(wait) as res:
print(json.load(res))await fetch(`https://cooperemail.com/api/v1/inboxes/${inboxId}/phone-number`, {
method: "POST",
headers: {
authorization: `Bearer ${key}`,
"content-type": "application/json",
"idempotency-key": "phone-1",
},
body: JSON.stringify({ area_code: "415" }),
});
const inbound = await fetch(
`https://cooperemail.com/api/v1/inboxes/${inboxId}/events?types=message.received&wait=25`,
{ headers: { authorization: `Bearer ${key}` } },
);{
"tool": "cooper_provision_phone_number",
"arguments": { "inbox_id": "research-bot", "area_code": "415", "idempotency_key": "phone-1" }
}
{
"tool": "cooper_wait_for_sms",
"arguments": { "inbox_id": "research-bot", "wait": 20 }
}Approve before send
A verified owner is required. PATCH send_mode to approval holds POST /messages, reply, reply-all, and forward as a pending draft and emails that owner Approve and Reject links. No verified owner returns HTTP 409 owner_required and leaves send_mode direct.
curl -s -X PATCH https://cooperemail.com/api/v1/inboxes/$INBOX \
-H "authorization: Bearer $COOP_KEY" \
-H "content-type: application/json" \
-d '{"send_mode":"approval"}'
curl -s https://cooperemail.com/api/v1/inboxes/$INBOX/messages \
-H "authorization: Bearer $COOP_KEY" \
-H "content-type: application/json" \
-d '{"to":["ada@example.com"],"subject":"Quote","text":"Ready for review.","client_id":"draft-1"}'import json, os, urllib.request
inbox = os.environ["INBOX"]
key = os.environ["COOP_KEY"]
def post(path, payload, method="POST"):
req = urllib.request.Request(
f"https://cooperemail.com{path}",
data=json.dumps(payload).encode(),
headers={"authorization": f"Bearer {key}", "content-type": "application/json"},
method=method,
)
with urllib.request.urlopen(req) as res:
return json.load(res)
post(f"/api/v1/inboxes/{inbox}", {"send_mode": "approval"}, "PATCH")
post(f"/api/v1/inboxes/{inbox}/messages", {
"to": ["ada@example.com"],
"subject": "Quote",
"text": "Ready for review.",
"client_id": "draft-1",
})await fetch(`https://cooperemail.com/api/v1/inboxes/${inboxId}`, {
method: "PATCH",
headers: {
authorization: `Bearer ${key}`,
"content-type": "application/json",
},
body: JSON.stringify({ send_mode: "approval" }),
});
await fetch(`https://cooperemail.com/api/v1/inboxes/${inboxId}/messages`, {
method: "POST",
headers: {
authorization: `Bearer ${key}`,
"content-type": "application/json",
},
body: JSON.stringify({
to: ["ada@example.com"],
subject: "Quote",
text: "Ready for review.",
client_id: "draft-1",
}),
});{
"tool": "cooper_update_inbox",
"arguments": { "inbox_id": "research-bot", "send_mode": "approval" }
}
{
"tool": "cooper_create_draft",
"arguments": {
"inbox_id": "research-bot",
"to": ["ada@example.com"],
"subject": "Quote",
"text": "Ready for review.",
"client_id": "draft-1"
}
}