Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ No account yet? Give your agent this prompt:

> Read https://mailly.site/skill.md and reserve my chosen Mailly inbox name using HTTP; ask me for the name if unknown. If taken, ask me for another name; no blind retries. Give me only the verification link. After I sign in/register with my verified external email and explicitly bind this chosen inbox on the web page, report the address and completion, then STOP. Do not obtain credentials, read mail, send mail or authorize account access.

No agent, CLI, Node.js or MCP connection is required: you can start directly at https://mailly.site/onboard using the same API. Public `POST https://api.mailly.site/onboarding/start` without authentication, with `Content-Type: application/json` and JSON `{"name":"chosen-name"}`, returns `address` (string `chosen-name@mailly.site`), `request_id`, `verification_url`, `status_url`, temporary `setup_token`, `expires_in: 1800` and `interval: 5`. Choose your preferred name; agents must ask if unknown. Names are trimmed/lowercased, 1–64 characters from `a-z0-9._-`, start/end alphanumeric; reserved system names are rejected. The name is reserved for 30 minutes against existing inboxes and pending reservations. HTTP 409 `code=name_taken`: ask for another name, never blindly mass-retry. Expiration or cancellation releases it. Starting creates no user or receiving inbox; anonymous receiving and reading are deferred. The agent keeps the token private and gives you only the verification URL. Sign in/register with your verified external email (not mailly.site or a subdomain), opening the magic link yourself, then explicitly bind this chosen inbox—not agent account access.
No agent, CLI, Node.js or MCP connection is required: you can start directly at https://mailly.site/onboard using the same API. Public `POST https://api.mailly.site/onboarding/start` without authentication, with `Content-Type: application/json` and JSON `{"name":"chosen-name"}`, returns `address` (string `chosen-name@mailly.site`), `request_id`, `verification_url`, `status_url`, temporary `setup_token`, `expires_in: 1800` and `interval: 5`. Choose your preferred name; agents must ask if unknown. Names are trimmed/lowercased, 3–64 characters from `a-z0-9._-`, start/end alphanumeric; system and official names such as `admin`, `support`, `info` and `sys` are rejected (compared ignoring `._-`). The name is reserved for 30 minutes against existing inboxes and pending reservations. HTTP 409 `code=name_taken`: ask for another name, never blindly mass-retry. Expiration or cancellation releases it. Starting creates no user or receiving inbox; anonymous receiving and reading are deferred. The agent keeps the token private and gives you only the verification URL. Sign in/register with your verified external email (not mailly.site or a subdomain), opening the magic link yourself, then explicitly bind this chosen inbox—not agent account access.

The agent polls `GET status_url` with `Authorization: Bearer <setup_token>` at least 5 seconds apart: pending is HTTP 202, denied is HTTP 200, ready is HTTP 200 `{success:true,status:"ready",address:{id,address},hint}`. Setup expires in 30 minutes; ready status remains retrievable for 10 minutes after human confirmation. It returns no `api_key` or other mailbox credentials. The token only polls this setup: it cannot read mail, send or access the account. Denial, expiration or rate limiting stops setup without automatic restart. Only confirmation binds the chosen inbox within account quotas. Never substitute an existing account inbox or an auto-generated name; there is no multi-inbox selection. The agent reports the address and completion, then stops. This public HTTP exception adds no CLI command or MCP tool.

Expand Down
4 changes: 2 additions & 2 deletions skills/mailly/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Read https://mailly.site/skill.md to start. Use HTTP directly; do not require No

## Set up an inbox

1. Ask the human for their desired inbox name if unknown; never invent one. Send `POST https://api.mailly.site/onboarding/start` with **no authentication**, `Content-Type: application/json` and JSON `{"name":"chosen-name"}`. Names are trimmed and lowercased, must be 1–64 characters from `a-z0-9._-`, start and end alphanumeric, and cannot be reserved system names. The response contains `address` (string `chosen-name@mailly.site`), `request_id`, `verification_url`, `status_url`, `setup_token`, `expires_in: 1800` and `interval: 5`. It reserves the name for 30 minutes against existing inboxes and pending reservations, creating neither a user nor a receiving inbox. HTTP 409 `code=name_taken`: ask for another name; no blind or mass retries. Expiration or cancellation releases the reservation.
1. Ask the human for their desired inbox name if unknown; never invent one. Send `POST https://api.mailly.site/onboarding/start` with **no authentication**, `Content-Type: application/json` and JSON `{"name":"chosen-name"}`. Names are trimmed and lowercased, must be 3–64 characters from `a-z0-9._-`, start and end alphanumeric, and cannot be reserved system or official names such as `admin`, `support`, `info` or `sys` (compared ignoring `._-`, so `no.reply` is also rejected). HTTP 400 means the name breaks these rules: tell the human why and ask for another. The response contains `address` (string `chosen-name@mailly.site`), `request_id`, `verification_url`, `status_url`, `setup_token`, `expires_in: 1800` and `interval: 5`. It reserves the name for 30 minutes against existing inboxes and pending reservations, creating neither a user nor a receiving inbox. HTTP 409 `code=name_taken`: ask for another name; no blind or mass retries. Expiration or cancellation releases the reservation.
2. Give **only `verification_url`** to the human. They sign in or register using their verified external email (not `mailly.site` or any subdomain), open its magic link themselves, then explicitly bind **this chosen inbox** on the web page within account quotas. This does **not grant agent account access**. No receiving inbox exists until human confirmation; anonymous receiving and reading are deferred. Humans can also start directly at https://mailly.site/onboard using the same API; no agent is required.
3. Keep `setup_token` and `status_url` privately in secure credential storage for resume. Never put tokens, API keys or magic links in chat, logs, public configuration or committed files. Do not automate verification or consent, request a password or verification code, or read the human's external inbox to redeem the setup email.
4. After handing over the URL, poll `GET status_url` with `Authorization: Bearer <setup_token>` at intervals of **at least 5 seconds** (respect a longer returned interval). Resume with the same private token and URL, not a new setup request.
Expand All @@ -24,7 +24,7 @@ The human can receive and read mail in the authenticated web console and view AP

Do not retrieve or scrape the human's console, collect API keys through chat, auto-authorize MCP/CLI as a fallback, or read the inbox immediately after setup. Report completion and stop even if the human has existing inboxes or an empty inbox. Do not fabricate delivery or automatically send a test email.

中文:先询问人类想要的名称,通过 HTTP 预留 30 分钟;名称冲突请人类另选,禁止盲目批量重试。也可直接在 https://mailly.site/onboard 开始,无需 Agent。人类用已验证外部邮箱登录或注册,再按账户额度明确绑定这个邮箱;不得替换为已有邮箱或自动名称,不提供多邮箱选择。预留不创建用户或收件邮箱,过期或取消释放名称,匿名收件和读信暂缓。临时令牌只查询本次开通;ready 不返回 API Key 或其他凭据。Agent 报告地址与完成后立即停止。用户可在已登录工作台收件、读信,在 `/app/developers` 查看 API Key,自行决定后续接入;禁止抓取工作台、在聊天收集密钥或自动发起 MCP/CLI 授权。
中文:先询问人类想要的名称(3–64 个字符,不能用 admin、support、info、sys 等系统和官方用途名称),通过 HTTP 预留 30 分钟;名称冲突请人类另选,禁止盲目批量重试。也可直接在 https://mailly.site/onboard 开始,无需 Agent。人类用已验证外部邮箱登录或注册,再按账户额度明确绑定这个邮箱;不得替换为已有邮箱或自动名称,不提供多邮箱选择。预留不创建用户或收件邮箱,过期或取消释放名称,匿名收件和读信暂缓。临时令牌只查询本次开通;ready 不返回 API Key 或其他凭据。Agent 报告地址与完成后立即停止。用户可在已登录工作台收件、读信,在 `/app/developers` 查看 API Key,自行决定后续接入;禁止抓取工作台、在聊天收集密钥或自动发起 MCP/CLI 授权。

## Optional reference for separately authorized workflows

Expand Down
2 changes: 1 addition & 1 deletion skills/mailly/reference/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ mailly inbox webhook endpoints test <endpoint-id> --json
mailly inbox webhook endpoints delete <endpoint-id> --json
```

`--domain` 必须已经通过归属和 MX 验证;`--prefix` 可省略并由服务端随机生成。Webhook `set` / `endpoints update` 省略 `--body-format` 或去引用选项时保留现有值;用 `--no-strip-quotes` 可显式关闭。签名密钥只在首次配置(`set` / `endpoints add`)或 `--rotate-secret` 时返回一次,立即保存且不要写入仓库或日志。
`--domain` 必须已经通过归属和 MX 验证;`--prefix` 可省略并由服务端随机生成。平台域名 `mailly.site` 上的前缀至少 3 个字符,且不能用 `admin`、`support`、`info`、`sys` 等系统和官方用途名称(忽略 `._-` 比对);自有域名不受这两条限制。Webhook `set` / `endpoints update` 省略 `--body-format` 或去引用选项时保留现有值;用 `--no-strip-quotes` 可显式关闭。签名密钥只在首次配置(`set` / `endpoints add`)或 `--rotate-secret` 时返回一次,立即保存且不要写入仓库或日志。

- **默认 Webhook 与专属端点**:地址只要有一个启用的专属端点,它的来信就只推给它自己的端点(全部启用端点各收一份),不再推默认 Webhook;没有启用端点时回落到默认 Webhook。同一地址下 URL 不能重复。旧的 `mailly inbox webhook address get|set|delete <address-id>` 仍可用,但只作用于该地址最早创建的端点;需要多个端点时用 `endpoints`。
- **测试**:`webhook test` / `endpoints test` 会立即向该 URL POST 一条签名的 `webhook.test` 事件(请求头 `Mailly-Event-Type: webhook.test`,body 带 `test: true`,结构与该端点格式的 `email.received` 相同,但示例邮件不入库,`content_url` 取不到),返回 `test.ok`、`responseStatus`、`error`、`durationMs`,并记到 `lastTest`;不入队也不重试,停用的端点也能测。端点未返回 2xx 时 CLI 退出码为 1。同一端点 5 秒内只能测一次(429)。测试会真实打到用户的接收端,接收端若未按 `Mailly-Event-Type` 或 `test` 过滤可能把它当成真实来信处理——只在用户要求验证时执行,不要例行调用。
Expand Down
2 changes: 1 addition & 1 deletion tests/skill-contract.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ test('Mailly entry bootstraps through public HTTP without CLI or MCP prerequisit
assert.match(skill, /no authentication/);
assert.match(skill, /JSON `\{"name":"chosen-name"\}`/);
assert.match(skill, /Ask the human.*name if unknown/);
assert.match(skill, /trimmed and lowercased.*1–64.*a-z0-9\._-.*start and end alphanumeric.*reserved system names/);
assert.match(skill, /trimmed and lowercased.*3–64.*a-z0-9\._-.*start and end alphanumeric.*reserved system or official names/);
assert.match(skill, /`address` \(string `chosen-name@mailly\.site`\)/);
assert.match(skill, /30 minutes against existing inboxes and pending reservations/);
assert.match(skill, /HTTP 409 `code=name_taken`: ask for another name; no blind or mass retries/);
Expand Down
Loading