skills/cf-temp-mail-agent-mail/SKILL.md
Read and send mails from a cloudflare_temp_email mailbox using a user-supplied Address JWT and API base URL. Use when the user (or an agent such as OpenClaw / Codex / Cursor) needs to list the inbox, fetch a specific message, or send an email via the server-parsed /api/parsed_mails, /api/parsed_mail/:id, and /api/send_mail endpoints. Falls back to local parsing of /api/mail/:id raw source with mail-parser-wasm + postal-mime if the parsed endpoints are unavailable. Does NOT handle mailbox creation — the user provides the JWT themselves.
npx skillsauth add find-xposed-magisk/cloudflare_temp_email cf-temp-mail-agent-mailInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
The user must first open the frontend (e.g. https://mail.example.com) in a browser and create or log into a mailbox address. This step may require passing a Turnstile CAPTCHA that agents cannot complete. After that, the Address JWT is displayed in the frontend UI and can be copied directly.
BASE — API base URL, e.g. https://mail.example.com.JWT — Address JWT, visible and copyable from the frontend UI after creating or logging into a mailbox.SITE_PASSWORD — only if the deployment enabled x-custom-auth.If anything is missing, ask the user before making requests.
To avoid asking every time, save credentials to ~/.cf-temp-mail/credentials.json:
{
"base": "https://mail.example.com",
"jwt": "<ADDRESS_JWT>",
"site_password": ""
}
On first use, if the file exists, read and use it. If not, ask the user and save for next time. Before each request, validate the JWT via GET /api/settings — if it returns 401, inform the user the JWT is expired and ask for a fresh one, then update the file.
Authorization: Bearer <JWT> — on every /api/* request.x-custom-auth: <SITE_PASSWORD> — only when the site requires it.x-lang: en or zh — optional, error-message language.Do not send the Address JWT as x-user-token — that is a different JWT type and will yield 401 InvalidAddressCredentialMsg.
| Task | Method | Path | Returns |
| ------------------- | ------ | ---------------------------------- | ----------------------------------------- |
| Address info | GET | /api/settings | { address, send_balance } |
| List parsed mails | GET | /api/parsed_mails?limit=&offset= | { results: [parsedMail], count } |
| Get one parsed mail | GET | /api/parsed_mail/:id | parsedMail |
limit 1–100, offset 0-based. On 429, back off.
parsedMail shape:
{
"id": 42,
"message_id": "<...>",
"source": "[email protected]",
"to": "[email protected]",
"created_at": "2026-04-21 10:00:00",
"sender": "Foo <[email protected]>",
"subject": "Your code is 123456",
"text": "Your code is 123456\n",
"html": "<p>Your code is <b>123456</b></p>",
"attachments": [
{ "filename": "a.pdf", "mimeType": "application/pdf", "disposition": "attachment", "size": 12345 }
]
}
Attachments carry metadata only; no binary content.
curl -s "$BASE/api/settings" -H "Authorization: Bearer $JWT"
# → { "address": "[email protected]", "send_balance": 0 }
If this returns 401, JWT is wrong / expired / mismatched with BASE — ask the user for a fresh one.
curl -s "$BASE/api/parsed_mails?limit=20&offset=0" \
-H "Authorization: Bearer $JWT"
curl -s "$BASE/api/parsed_mail/<id>" -H "Authorization: Bearer $JWT"
Requires send_balance > 0 (check via /api/settings). The deployment must have a send method configured (Resend / SMTP / Cloudflare Email Routing binding).
| Task | Method | Path | Body / Returns |
| ----------------------- | ------ | ------------------------------- | ------------------------------------------------- |
| Request send access | POST | /api/request_send_mail_access | {} → { status: "ok" } |
| Send mail | POST | /api/send_mail | sendMailBody → { status: "ok" } |
| List sent (sendbox) | GET | /api/sendbox?limit=&offset= | { results: [...], count } |
| Delete sent item | DELETE | /api/sendbox/:id | { success: true } |
sendMailBody:
{
"from_name": "My Name",
"to_mail": "[email protected]",
"to_name": "Recipient",
"subject": "Hello",
"content": "<p>Hi</p>",
"is_html": true
}
from_name and to_name are optional (can be empty string). is_html: false sends plain text.
curl -s -X POST "$BASE/api/send_mail" \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{"from_name":"","to_mail":"[email protected]","to_name":"","subject":"Test","content":"Hello","is_html":false}'
If /api/parsed_mails / /api/parsed_mail/:id returns 404 (older deployment) or a parse error, fall back to /api/mails / /api/mail/:id (RFC822 raw) and parse locally. Mirror the frontend strategy in frontend/src/utils/email-parser.js: try mail-parser-wasm first, fall back to postal-mime.
npm i mail-parser-wasm postal-mime
// parseRaw.mjs — drop-in parser matching frontend behavior
async function parseRaw(raw) {
try {
const { parse_message } = await import('mail-parser-wasm');
const m = parse_message(raw);
if (m?.subject && (m?.body_html || m?.text)) {
return {
sender: m.sender || '',
subject: m.subject || '',
text: m.text || '',
html: m.body_html || '',
attachments: (m.attachments || []).map(a => ({
filename: a.filename || a.content_id || '',
mimeType: a.content_type || '',
size: a.content?.length ?? 0,
})),
};
}
} catch { /* fall through */ }
const PostalMime = (await import('postal-mime')).default;
const p = await PostalMime.parse(raw);
const sender = p.from?.name && p.from?.address
? `${p.from.name} <${p.from.address}>`
: (p.from?.address || '');
return {
sender,
subject: p.subject || '',
text: p.text || '',
html: p.html || '',
attachments: (p.attachments || []).map(a => ({
filename: a.filename || a.contentId || '',
mimeType: a.mimeType || '',
size: a.content?.length ?? 0,
})),
};
}
// usage
const row = await (await fetch(`${BASE}/api/mail/${id}`, {
headers: { Authorization: `Bearer ${JWT}` },
})).json();
const parsed = await parseRaw(row.raw);
For attachment bytes, use postal-mime directly — parsed.attachments[i].content is a Uint8Array.
poll=3s, exponential backoff capped at 10s.id.429 — sleep and retry.401 InvalidAddressCredentialMsg — JWT wrong/expired/sent via wrong header. Ask the user for a fresh JWT.401 CustomAuthPasswordMsg — site requires x-custom-auth; attach SITE_PASSWORD.400 InvalidLimitMsg / InvalidOffsetMsg — limit must be 1..100, offset ≥ 0.404 on /api/parsed_mail* — deployment predates the parsed endpoints; use the fallback.429 — rate limited; back off.development
Maintainer-only workflow for handling GitHub Secret Scanning alerts on OpenClaw. Use when Codex needs to triage, redact, clean up, and resolve secret leakage found in issue comments, issue bodies, PR comments, or other GitHub content.
development
Maintainer workflow for OpenClaw releases, prereleases, changelog release notes, and publish validation. Use when Codex needs to prepare or verify stable or beta release steps, align version naming, assemble release notes, check release auth requirements, or validate publish-time commands and artifacts.
development
Run, watch, debug, and extend OpenClaw QA testing with qa-lab and qa-channel. Use when Codex needs to execute the repo-backed QA suite, inspect live QA artifacts, debug failing scenarios, add new QA scenarios, or explain the OpenClaw QA workflow. Prefer the live OpenAI lane with regular openai/gpt-5.4 in fast mode; do not use gpt-5.4-pro or gpt-5.4-mini unless the user explicitly overrides that policy.
development
End-to-end Parallels smoke, upgrade, and rerun workflow for OpenClaw across macOS, Windows, and Linux guests. Use when Codex needs to run, rerun, debug, or interpret VM-based install, onboarding, gateway smoke tests, latest-release-to-main upgrade checks, fresh snapshot retests, or optional Discord roundtrip verification under Parallels.