API Documentation
YYDS Mail provides a RESTful API for external integrations. This page organizes the core integration guidance into six themes and also lists the public query endpoints that remain callable without signing in. All endpoints are prefixed with /v1.
Quick Start
- Sign in via GitHub or LinuxDo OAuth at /login
- Navigate to API Key Management and create a key
- Use the key in the
X-API-Keyheader for API calls
curl https://maliapi.215.im/v1/accounts \ -X POST \ -H "X-API-Key: AC-your_api_key"
Base URL
https://maliapi.215.im/v1Authentication Methods
Bearer Token (JWT)
JWT access token from login or OAuth.
Authorization: Bearer <access_token>API Key
API key with AC- prefix. Supports domain scope (all/public/own/specific) for automation flows, while owner endpoints under `/v1/me/domains*` and `/v1/me/wildcard-rules*` require Bearer JWT.
X-API-Key: AC-...Temp Token
Short-lived token returned when creating a temp inbox.
Authorization: Bearer <temp_token>Public query endpoints
These endpoints are still publicly callable and are useful for anonymous discovery, pricing display, public stats, and tooling.
GET /v1/domains GET /v1/platforms GET /v1/plans GET /v1/pricing GET /v1/domain-reward/config GET /v1/stats
Temporary Email API
Create disposable email inboxes with no registration required. Emails auto-delete after 24 hours.
/v1/accountsCreate a temporary inbox under a normal domain, or through a wildcard rule that issues a real child domain.
Request rules
- Use `domain` for fixed-domain creation.
- For wildcard creation, keep sending `domain` and add an optional `subdomain`.
- `localPart` is the preferred request field; the legacy `address` field remains compatible.
- When `subdomain` is omitted, `POST /v1/accounts` keeps fixed-domain behavior, while `POST /v1/accounts/wildcard` will use the default or a random child domain.
- Once a child domain is pinned, different local parts can reuse it as long as the final email addresses stay unique.
- Only legacy integrations and advanced debugging should care about `wildcardRuleId` or `subdomainLabel`; normal scripts should think in `domain + subdomain` only.
- After creation, every follow-up call to `POST /v1/token`, `GET /v1/messages`, and `GET /v1/messages/{id}` must use the final `address` returned by the create response.
- For owner-side custom-domain onboarding, the default DNS flow only needs `TXT + MX`; wildcard mode adds one `wildcard MX`; no extra `CNAME` is required.
Auth: API Key / Bearer JWT / YYDS Mail Web App
Request Body
{
"localPart": "my-prefix",
"platformCode": "github",
"excludeDomains": ["blocked.example.com"]
}Response
{
"success": true,
"data": {
"id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"address": "my-prefix@public.example.com",
"mode": "fixed",
"domain": "public.example.com",
"subdomain": "",
"token": "eyJhbGciOiJIUzI1NiIs...",
"inboxType": "temp",
"source": "api",
"expiresAt": "2026-03-15T12: 00: 00Z",
"isActive": true,
"createdAt": "2026-03-14T12: 00: 00Z",
"selection": {
"domainId": "domain_public_001",
"domain": "public.example.com",
"platformCode": "github",
"platformStatus": "recently_available",
"fallbackUsed": false,
"excludedCount": 2
}
}
}Example
curl https://maliapi.215.im/v1/accounts \ -X POST \ -H "X-API-Key: AC-your-key" \ -H "Content-Type: application/json" \ -d '{"localPart":"my-prefix","platformCode":"github","excludeDomains":["blocked.example.com"]}'
Normal domain example
{
"localPart": "my-prefix",
"domain": "public.example.com"
}Wildcard example with automatic child domain (/v1/accounts/wildcard recommended)
{
"success": true,
"data": {
"mode": "wildcard",
"address": "my-prefix@a3f9c2.public.example.com",
"domain": "a3f9c2.public.example.com",
"subdomain": "a3f9c2"
}
}Wildcard example using key defaults
{
"localPart": "my-prefix"
}Wildcard rule with fixed child domain
{
"localPart": "my-prefix",
"domain": "public.example.com",
"subdomain": "team-a"
}Target a platform and exclude domains for one request
{
"localPart": "my-prefix",
"platformCode": "github",
"excludeDomains": ["blocked.example.com"],
"excludeDomainIds": ["domain_skip_001"]
}/v1/accounts/wildcardCreate a temporary inbox under a normal domain, or through a wildcard rule that issues a real child domain.
For wildcard creation, keep sending `domain` and add an optional `subdomain`.
Auth: API Key / Bearer JWT / YYDS Mail Web App
Request Body
{
"localPart": "my-prefix",
"domain": "public.example.com",
"subdomain": "optional"
}Response
{
"success": true,
"data": {
"id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"address": "my-prefix@mail.example.com",
"mode": "wildcard",
"domain": "mail.example.com",
"subdomain": "mail",
"token": "eyJhbGciOiJIUzI1NiIs...",
"inboxType": "temp",
"source": "api",
"expiresAt": "2026-03-15T12: 00: 00Z",
"isActive": true,
"createdAt": "2026-03-14T12: 00: 00Z"
}
}Example
curl https://maliapi.215.im/v1/accounts/wildcard \ -X POST \ -H "X-API-Key: AC-your-key" \ -H "Content-Type: application/json" \ -d '{"localPart":"my-prefix","domain":"public.example.com","subdomain":"team-a"}'
/v1/tokenAuth: Temp token (same inbox)
Request Body
{
"address": "k7xm2pa9bf@public.example.com"
}Response
{
"success": true,
"data": {
"id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"address": "k7xm2pa9bf@public.example.com",
"token": "eyJhbGciOiJIUzI1NiIs..."
}
}Example
curl https://maliapi.215.im/v1/token \ -X POST \ -H "Authorization: Bearer <temp_token>" \ -H "Content-Type: application/json" \ -d '{"address":"k7xm2pa9bf@public.example.com"}'
Refresh the token with the final returned address
{
"address": "my-prefix@mail.team-a.example.com"
}/v1/accounts/meAuth: Temp Token
Response
{
"success": true,
"data": {
"id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"address": "k7xm2pa9bf@public.example.com",
"inboxType": "temp",
"source": "web",
"expiresAt": "2026-03-15T12: 00: 00Z",
"isActive": true,
"messageCount": 3,
"createdAt": "2026-03-14T12: 00: 00Z"
}
}Example
curl https://maliapi.215.im/v1/accounts/me \ -H "Authorization: Bearer <temp_token>"
/v1/inboxes/{id}Auth: Temp token / API key / Bearer JWT
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Temporary inbox ID |
Response
{
"success": true,
"data": {
"id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"address": "k7xm2pa9bf@public.example.com",
"inboxType": "temp",
"source": "api",
"expiresAt": "2026-03-15T12: 00: 00Z",
"isActive": true,
"messageCount": 0,
"createdAt": "2026-03-14T12: 00: 00Z"
}
}Example
curl https://maliapi.215.im/v1/inboxes/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4 \ -H "Authorization: Bearer <temp_token>"
/v1/accounts/{id}DeprecatedAuth: Temp token / API key / Bearer JWT
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Temporary inbox ID |
Example
curl https://maliapi.215.im/v1/accounts/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4 \ -H "Authorization: Bearer <temp_token>"
/v1/accounts/{id}Auth: Temp Token
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Temporary inbox ID |
Response
204 No Content
Example
curl https://maliapi.215.im/v1/accounts/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4 \ -X DELETE \ -H "Authorization: Bearer <temp_token>"
API Key Platform-Aware Domain Routing
Let scripts declare the registration platform and automatically avoid manually excluded or confirmed-restricted domains within the API key's existing scope. Use `platformCode` for the target and `excludeDomains` / `excludeDomainIds` for request-only exclusions. A successful response includes `selection` with the chosen domain, platform status, filtered count, and fallback result.
/v1/platformsResponse
{
"success": true,
"data": {
"platforms": [
{
"code": "github",
"name": "GitHub",
"category": "registration",
"aliases": ["githubusercontent.com"],
"websiteDomain": "github.com",
"iconSlug": "github",
"brandColor": "181717",
"source": "seed",
"isActive": true,
"sortOrder": 10
}
]
}
}Example
curl https://maliapi.215.im/v1/platforms
/v1/me/platform-suggestions/detectAuth: Bearer JWT
Request Body
{
"inboxId": "inbox_recent_001"
}Response
{
"success": true,
"data": {
"matched": true,
"confidence": "high",
"reason": "official_domain",
"inboxId": "inbox_recent_001",
"messageId": "message_001",
"senderAddress": "noreply@github.com",
"platform": {
"code": "github",
"name": "GitHub",
"websiteDomain": "github.com",
"iconSlug": "github",
"brandColor": "181717"
}
}
}Example
curl https://maliapi.215.im/v1/me/platform-suggestions/detect -X POST -H "Authorization: Bearer <access_token>" -H "Content-Type: application/json" -d '{"inboxId":"inbox_recent_001"}'
/v1/me/platform-suggestionsAuth: Bearer JWT
Request Body
{
"name": "Acme Cloud",
"websiteDomain": "acme.example",
"inboxId": "inbox_recent_001",
"messageId": "message_001"
}Response
{
"success": true,
"data": {
"suggestion": {
"id": "suggestion_001",
"name": "Acme Cloud",
"websiteDomain": "acme.example",
"status": "pending",
"supportCount": 3,
"submittedByMe": true
}
}
}Example
curl https://maliapi.215.im/v1/me/platform-suggestions -X POST -H "Authorization: Bearer <access_token>" -H "Content-Type: application/json" -d '{"name":"Acme Cloud","websiteDomain":"acme.example","inboxId":"inbox_recent_001","messageId":"message_001"}'
/v1/me/platform-suggestionsAuth: Bearer JWT
Example
curl https://maliapi.215.im/v1/me/platform-suggestions -H "Authorization: Bearer <access_token>"
/v1/me/api-keys/{id}/domain-policyAuth: Bearer JWT
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | API Key ID |
Response
{
"success": true,
"data": {
"apiKeyId": "key_001",
"configured": true,
"defaultPlatformCode": "github",
"avoidMode": "confirmed",
"fallbackMode": "smart",
"exclusions": [
{
"domainId": "domain_skip_001",
"domain": "blocked.example.com",
"platformCode": "github",
"reasonCode": "manual",
"source": "manual"
}
]
}
}Example
curl https://maliapi.215.im/v1/me/api-keys/key_001/domain-policy \ -H "Authorization: Bearer <access_token>"
/v1/me/api-keys/{id}/domain-policyAuth: Bearer JWT
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | API Key ID |
Request Body
{
"defaultPlatformCode": "github",
"avoidMode": "confirmed",
"fallbackMode": "smart",
"exclusions": [
{
"domainId": "domain_skip_001",
"platformCode": "github",
"reasonCode": "manual"
}
]
}Response
{
"success": true,
"data": {
"apiKeyId": "key_001",
"configured": true,
"defaultPlatformCode": "github",
"avoidMode": "confirmed",
"fallbackMode": "smart",
"exclusions": [
{
"domainId": "domain_skip_001",
"domain": "blocked.example.com",
"platformCode": "github",
"reasonCode": "manual",
"source": "manual"
}
]
}
}Example
curl https://maliapi.215.im/v1/me/api-keys/key_001/domain-policy \ -X PUT \ -H "Authorization: Bearer <access_token>" \ -H "Content-Type: application/json" \ -d '{"defaultPlatformCode":"github","avoidMode":"confirmed","fallbackMode":"smart","exclusions":[{"domainId":"domain_skip_001","platformCode":"github","reasonCode":"manual"}]}'
/v1/me/domains/{id}/platform-statusAuth: Bearer Token / API Key
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Domain ID |
Response
{
"success": true,
"data": {
"domainId": "domain_public_001",
"domain": "public.example.com",
"reportMaxAgeHours": 72,
"statuses": [
{
"platformCode": "github",
"platformName": "GitHub",
"platformIconSlug": "github",
"platformBrandColor": "181717",
"status": "suspected_restricted",
"effectiveStatus": "suspected_restricted",
"reportCount": 4,
"positiveCount": 1,
"negativeCount": 3,
"strongNegativeCount": 3,
"confidence": 0.75,
"platformIncident": false
}
]
}
}Example
curl https://maliapi.215.im/v1/me/domains/domain_public_001/platform-status \ -H "X-API-Key: AC-your-key"
/v1/me/domain-platform-reportsAuth: Bearer Token / API Key
Request Body
{
"domainId": "domain_public_001",
"platformCode": "github",
"inboxId": "inbox_recent_001",
"outcome": "blocked",
"reasonCode": "disposable_email_blocked",
"excludeForApiKey": true,
"apiKeyId": "key_001"
}Response
{
"success": true,
"data": {
"report": {
"domainId": "domain_public_001",
"platformCode": "github",
"inboxId": "inbox_recent_001",
"outcome": "blocked",
"reasonCode": "disposable_email_blocked",
"evidenceStrength": "strong"
},
"status": {
"effectiveStatus": "watching",
"reportCount": 1
}
}
}Example
curl https://maliapi.215.im/v1/me/domain-platform-reports \ -X POST \ -H "X-API-Key: AC-your-key" \ -H "Content-Type: application/json" \ -d '{"domainId":"domain_public_001","platformCode":"github","inboxId":"inbox_recent_001","outcome":"blocked","reasonCode":"disposable_email_blocked","excludeForApiKey":true}'
Messages
Read, manage, and delete email messages. Supports temp token, API key, or JWT authentication.
/v1/inboxes/{id}/messagesAuth: Temp token / API key / Bearer JWT
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Temporary inbox ID |
Example
curl "https://maliapi.215.im/v1/inboxes/f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3/messages?seen=false&limit=20 class="code-string">" \ -H "Authorization: Bearer <token>"
/v1/messagesAuth: Temp token / API key / Bearer JWT
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| address | string | No | Inbox address (query param, required for JWT / API key users) |
| limit | number | No | Maximum number of messages to return (default: 50) |
| seen | boolean | No | Optional. Filter by read state: true returns only read messages, false only unread. |
| since | string | No | Optional. RFC3339 timestamp (inclusive); only messages at or after this time, e.g. 2026-01-02T15:04:05Z. |
| q | string | No | Optional. Case-insensitive substring search across subject and sender (name/address). |
| after_id | string | No | Optional. Cursor pagination: return messages after (older than) this message ID; use with nextCursor from the response. Takes precedence over offset. |
Response
{
"success": true,
"data": {
"messages": [
{
"id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"inbox_id": "f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3",
"inboxId": "f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3",
"from": { "name": "Sender", "address": "sender@example.com" },
"to": [{ "name": "", "address": "k7xm2pa9bf@public.example.com" }],
"subject": "Welcome!",
"seen": false,
"hasAttachments": false,
"size": 1234,
"createdAt": "2026-03-14T12: 30: 00Z"
}
],
"total": 1,
"unreadCount": 1
}
}Example
curl "https://maliapi.215.im/v1/messages?address=k7xm2pa9bf@public.example.com" \ -H "Authorization: Bearer <token>"
Use the final returned address when listing messages
# 创建邮箱后请保存返回值里的 address curl "https://maliapi.215.im/v1/messages?address=my-prefix@mail.team-a.example.com" \ -H "Authorization: Bearer <token>"
Filters + cursor pagination
# Fetch only unread, filtered by keyword curl "https://maliapi.215.im/v1/messages?address=k7xm2pa9bf@public.example.com&seen=false&q=verification&limit=20 class="code-string">" \ -H "Authorization: Bearer <token>" # Continue with the nextCursor from the previous page curl "https://maliapi.215.im/v1/messages?address=k7xm2pa9bf@public.example.com&seen=false&after_id=<nextCursor> class="code-string">" \ -H "Authorization: Bearer <token>"
When any filter parameter (seen / since / q / after_id) is used, the response also includes nextCursor: the ID of the last returned message when more matches remain, or an empty string. Requests without these parameters keep the legacy response shape unchanged.
/v1/messages/nextAuth: Temp token / API key (write) / Bearer JWT
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| address | string | No | Restrict to one inbox (optional). When omitted, the oldest unread across all of your active inboxes is taken. |
| wait | number | No | Long-poll seconds (0-30, optional). The server holds the request until an unread message arrives or the window expires (then 204). |
Response
{
"success": true,
"data": {
"message": {
"id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"from": { "name": "Example", "address": "noreply@example.com" },
"to": [{ "name": "", "address": "k7xm2pa9bf@public.example.com" }],
"subject": "Your verification code",
"text": "Your verification code is 384729.",
"html": ["<p>Your verification code is 384729.</p>"],
"seen": true,
"hasAttachments": false,
"size": 1234,
"createdAt": "2026-07-02T12: 30: 00Z",
"verificationCode": "384729"
},
"inboxAddress": "k7xm2pa9bf@public.example.com"
}
}
// When no unread message exists (after the wait window): 204 No ContentExample
curl "https://maliapi.215.im/v1/messages/next?address=k7xm2pa9bf@public.example.com&wait=30 class="code-string">" \ -H "X-API-Key: AC-xxxxxx"
/v1/messages/mark-readAuth: Temp token / API key (write) / Bearer JWT
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| address | string | No | Inbox address (query param, required for JWT / API key users) |
Response
{
"success": true,
"data": {
"mailbox": "k7xm2pa9bf@public.example.com",
"updated": 3,
"alreadySeen": 1,
"total": 4
}
}Example
curl "https://maliapi.215.im/v1/messages/mark-read?address=k7xm2pa9bf@public.example.com" \ -X POST \ -H "Authorization: Bearer <token>"
/v1/messages/{id}Auth: Temp token / API key / Bearer JWT
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Message ID |
| address | string | No | Inbox address (query param, required for JWT / API key users) |
Response
{
"success": true,
"data": {
"id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"from": { "name": "Sender", "address": "sender@example.com" },
"to": [{ "name": "", "address": "k7xm2pa9bf@public.example.com" }],
"subject": "Welcome!",
"text": "Hello, this is a test email. Your verification code is 384729.",
"html": ["<p>Hello, this is a test email.</p>"],
"seen": true,
"hasAttachments": true,
"size": 1234,
"createdAt": "2026-03-14T12: 30: 00Z",
"verificationCode": "384729",
"attachments": [
{
"id": "0",
"filename": "welcome.pdf",
"contentType": "application/pdf",
"size": 2048,
"downloadUrl": "/serve/mailbox/demo/message/attach/0/welcome.pdf"
}
]
}
}Example
curl "https://maliapi.215.im/v1/messages/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4?address=k7xm2pa9bf@public.example.com" \ -H "Authorization: Bearer <token>"
/v1/messages/{id}Auth: Temp token / API key (write) / Bearer JWT
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Message ID |
| address | string | No | Inbox address (query param, required for JWT / API key users) |
Request Body
{
"seen": true
}Response
{
"success": true,
"data": {
"id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"seen": true
}
}Example
curl "https://maliapi.215.im/v1/messages/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4?address=k7xm2pa9bf@public.example.com" \ -X PATCH \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"seen":true}'
Mark as unread
curl "https://maliapi.215.im/v1/messages/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4?address=k7xm2pa9bf@public.example.com" \ -X PATCH \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"seen":false}'
/v1/messages/{id}Auth: Temp token / API key (write) / Bearer JWT
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Message ID |
| address | string | No | Inbox address (query param, required for JWT / API key users) |
Response
204 No Content
Example
curl "https://maliapi.215.im/v1/messages/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4?address=k7xm2pa9bf@public.example.com" \ -X DELETE \ -H "Authorization: Bearer <token>"
/v1/sources/{id}Auth: Temp token / API key / Bearer JWT
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Message ID |
| address | string | No | Inbox address (query param, required for JWT / API key users) |
Response
{
"success": true,
"data": {
"id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"data": "Return-Path: <sender@example.com>\r\nFrom: Sender <sender@example.com>\r\nTo: k7xm2pa9bf@public.example.com\r\nSubject: Welcome!\r\n..."
}
}Example
curl "https://maliapi.215.im/v1/sources/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4?address=k7xm2pa9bf@public.example.com" \ -H "Authorization: Bearer <token>"
Webhook Management
Set up webhooks to receive real-time HTTP notifications when events occur.
/v1/me/webhooksAuth: Bearer Token / API Key
Response
{
"success": true,
"data": {
"webhooks": [
{
"id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"userId": "u_123",
"url": "https://example.com/hook",
"events": ["message.received", "message.deleted"],
"isActive": true,
"createdAt": "2026-03-14T12: 00: 00Z",
"updatedAt": "2026-03-14T12: 00: 00Z"
}
],
"total": 1
}
}Example
curl https://maliapi.215.im/v1/me/webhooks \ -H "Authorization: Bearer <token>"
/v1/me/webhooksAuth: Bearer JWT
Request Body
{
"url": "https://example.com/hook",
"events": ["message.received", "message.deleted"]
}Response
{
"success": true,
"data": {
"id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"userId": "u_123",
"url": "https://example.com/hook",
"events": ["message.received", "message.deleted"],
"secret": "e3b0c44298fc1c149afbf4c8996fb924...",
"hasSecret": true,
"secretHint": "e3b0c442",
"isActive": true,
"createdAt": "2026-03-14T12: 00: 00Z",
"updatedAt": "2026-03-14T12: 00: 00Z"
}
}Example
curl https://maliapi.215.im/v1/me/webhooks \ -X POST \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com/hook class="code-string">","events":["message.received","message.deleted"]}'
Signature Verification & Payload
Every delivery is signed with HMAC-SHA256. To verify: take the X-YYDS-Timestamp header and the raw request body, concatenate as "timestamp.body", compute HMAC-SHA256 with your webhook secret, and compare against the hex value after sha256= in the X-YYDS-Signature header. Reject stale timestamps (e.g. older than 5 minutes) to prevent replay. The payload contains message headers only, never the body — fetch content via GET /v1/messages/{id}?address=<mailbox>.
POST <your-webhook-url>
Content-Type: application/json
X-YYDS-Event: message.received
X-YYDS-Delivery: <delivery-id>
X-YYDS-Timestamp: <unix-seconds>
X-YYDS-Signature: sha256=HEX(HMAC-SHA256(secret, "<timestamp>.<raw-body>"))
{
"event": "message.received",
"deliveryId": "…",
"timestamp": "2026-07-02T10:30:00Z",
"source": "local",
"messageId": "…",
"mailbox": "you@example.com",
"from": { "name": "Alice", "address": "alice@example.com" },
"to": [{ "name": "", "address": "you@example.com" }],
"subject": "Hello",
"date": "2026-07-02T10:29:58Z",
"size": 2048,
"hasAttachments": false
}External account events: when a linked external IMAP account syncs new mail, message.received fires as well (payload carries source="external" plus accountId and accountEmail; local mail uses source="local"). Anti-storm aggregation: if one sync round ingests more than 50 new messages for an account, per-message events are replaced by a single messages.bulk_received aggregate (with count), delivered to webhooks subscribed to either messages.bulk_received or message.received.
{
"event": "messages.bulk_received",
"deliveryId": "…",
"timestamp": "2026-07-02T10:30:00Z",
"source": "external",
"mailbox": "you@gmail.com",
"count": 120,
"accountId": "ea_123",
"accountEmail": "you@gmail.com"
}Failed deliveries retry with exponential backoff: 1m / 5m / 30m / 2h / 6h (6 attempts total). After 20 consecutive failures the webhook is auto-disabled (re-enable it in the console after fixing your endpoint). Your endpoint must return 2xx within 10 seconds; redirects are not followed.
/v1/me/webhooks/{id}Auth: Bearer JWT
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Webhook ID |
Response
204 No Content
Example
curl https://maliapi.215.im/v1/me/webhooks/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4 \ -X DELETE \ -H "Authorization: Bearer <token>"
/v1/me/webhooks/{id}Auth: Bearer JWT
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Webhook ID |
Request Body
{
"url": "https://new-endpoint.example.com/hook",
"events": ["message.received", "message.deleted"],
"isActive": false
}Response
{
"success": true,
"data": {
"id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"userId": "u_123",
"url": "https://new-endpoint.example.com/hook",
"events": ["message.received", "message.deleted"],
"isActive": false,
"createdAt": "2026-03-14T12: 00: 00Z",
"updatedAt": "2026-03-21T08: 00: 00Z"
}
}Example
curl https://maliapi.215.im/v1/me/webhooks/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4 \ -X PATCH \ -H "Authorization: Bearer <jwt>" \ -H "Content-Type: application/json" \ -d '{"url":"https://new-endpoint.example.com/hook class="code-string">","isActive":false}'
/v1/me/webhooks/{id}/testAuth: Bearer JWT
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Webhook ID |
Response
{
"success": true,
"data": {
"success": true,
"statusCode": 200,
"latencyMs": 156
}
}Example
curl https://maliapi.215.im/v1/me/webhooks/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4/test \ -X POST \ -H "Authorization: Bearer <jwt>"
/v1/me/webhooks/{id}/rotate-secretAuth: Bearer JWT
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Webhook ID |
Response
{
"success": true,
"data": {
"webhookId": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"secret": "e3b0c44298fc1c149afbf4c8996fb924...",
"secretHint": "e3b0c442"
}
}Example
curl https://maliapi.215.im/v1/me/webhooks/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4/rotate-secret \ -X POST \ -H "Authorization: Bearer <jwt>"
/v1/me/webhooks/{id}/deliveriesAuth: Bearer Token / API Key
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Webhook ID |
| limit | number | No | Number of rows (default 20, max 50) |
| offset | number | No | Offset (default 0) |
Response
{
"success": true,
"data": {
"deliveries": [
{
"id": "d1e2f3…",
"webhookId": "a1b2c3d4…",
"event": "message.received",
"status": "failed",
"statusCode": 500,
"error": "endpoint returned HTTP 500",
"durationMs": 312,
"attemptCount": 2,
"maxAttempts": 6,
"nextAttemptAt": "2026-07-02T11: 05: 00Z",
"createdAt": "2026-07-02T10: 30: 00Z",
"attempts": [
{ "attemptNo": 1, "statusCode": 500, "durationMs": 280, "createdAt": "2026-07-02T10: 30: 01Z" },
{ "attemptNo": 2, "statusCode": 500, "durationMs": 312, "createdAt": "2026-07-02T10: 31: 02Z" }
]
}
],
"total": 1
}
}Example
curl "https://maliapi.215.im/v1/me/webhooks/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4/deliveries?limit=50" \ -H "Authorization: Bearer <token>"
AI / LLM Integration
Provides an llms.txt endpoint so AI assistants (ChatGPT, Claude, etc.) can quickly understand the public integration surface and supported auth methods.
/v1/llms.txtResponse
# yyds Mail API
> Public integration summary for developers and AI assistants.
> Base URL: https://maliapi.215.im/v1
## Public Scope
- Quick Start
- Temporary Email
- Platform-Aware Domain Routing
- Messages
- Webhooks
- Error Handling
Additional public query endpoints:
- GET /v1/domains
- GET /v1/platforms
- GET /v1/plans
- GET /v1/pricing
- GET /v1/domain-reward/config
- GET /v1/stats
## Authentication
- Bearer JWT: Authorization: Bearer <access_token>
- API Key: X-API-Key: AC-xxx
- Temp Token: Authorization: Bearer <temp_token>
- Temp tokens only work for temporary inbox flows
- Anonymous temp inbox creation is only available through the official homepage bridge
- Signed-in domain onboarding, wildcard rule management, and DNS automation are owner features inside the dashboard
- Domain platform status is separate from DNS/MX health and only affects platform-aware API-key selection
- DNS automation is optional. Without a service connection, custom-domain onboarding remains manual TXT + MX setup
## Temporary Email
POST /v1/accounts — Create a temporary inbox
POST /v1/token — Refresh the temp token for the same active inbox
GET /v1/accounts/me — Get current temp inbox profile
GET /v1/inboxes/{id} — Get inbox detail by ID (canonical)
GET /v1/accounts/{id} — DEPRECATED alias of GET /v1/inboxes/{id}
DELETE /v1/accounts/{id} — Deactivate a temporary inbox
## Platform-Aware Domain Routing
GET /v1/platforms — List supported platformCode values
GET /v1/me/api-keys/{id}/domain-policy — Read an API key routing policy (Bearer JWT)
PUT /v1/me/api-keys/{id}/domain-policy — Replace an API key routing policy (Bearer JWT)
GET /v1/me/domains/{id}/platform-status — Read effective platform status (Bearer JWT or scoped API key)
GET /v1/me/domain-platform-summary?domainIds=id1,id2 — Batch card summaries without N+1 requests
POST /v1/me/domain-platform-reports — Submit a result backed by a recent owned inbox
POST /v1/me/platform-suggestions/detect — Detect a platform from owned inbox metadata (Bearer JWT)
GET /v1/me/platform-suggestions — List your submitted platform candidates (Bearer JWT)
POST /v1/me/platform-suggestions — Submit or support a missing platform (Bearer JWT)
POST /v1/accounts accepts platformCode, excludeDomains, and excludeDomainIds for API-key selection. A successful response includes selection. Explicitly requesting an excluded domain returns HTTP 409; smart fallback only applies when domain is omitted. Existing keys keep legacy behavior when no policy is saved and platformCode is omitted.
## Messages
GET /v1/inboxes/{id}/messages — List messages by inbox ID (canonical)
GET /v1/messages?address=xxx — List messages for an inbox (address-keyed)
GET /v1/messages/next?address=xxx&wait=30 — Take the next unread message (auto marks read; response includes verificationCode; 204 when none)
POST /v1/messages/mark-read?address=xxx — Mark mailbox messages as read
GET /v1/messages/{id}?address=xxx — Get message detail (includes verificationCode when an OTP-style code is detected)
PATCH /v1/messages/{id}?address=xxx — Update message state
DELETE /v1/messages/{id}?address=xxx — Delete a message
GET /v1/sources/{id}?address=xxx — Get raw message source
## Webhooks
GET /v1/me/webhooks — List webhook subscriptions
POST /v1/me/webhooks — Create a webhook (signing secret shown once)
PATCH /v1/me/webhooks/{id} — Update a webhook
DELETE /v1/me/webhooks/{id} — Delete a webhook
POST /v1/me/webhooks/{id}/test — Send a signed test event
POST /v1/me/webhooks/{id}/rotate-secret — Rotate the webhook signing secret
GET /v1/me/webhooks/{id}/deliveries — List recent deliveries
## Error Handling
All errors follow the same envelope:
{ "success": false, "error": "...", "errorCode": "..." }
...Example
curl https://maliapi.215.im/v1/llms.txt
How to Use
Provide the llms.txt URL to any AI assistant, and it will understand the public integration surface and help you write client code.
# ChatGPT / Claude / other AI assistants
# Just point the AI to:
https://maliapi.215.im/v1/llms.txt
# The AI can then understand the public integration surface
# and help you write integration code.OpenAPI Specification
The complete machine-readable API contract (OpenAPI 3.1) — every public endpoint, auth scheme, and the shared response envelope, kept in sync with the server code.
https://maliapi.215.im/v1/openapi.yamlImport the spec URL directly into Postman (Import → Link) or any OpenAPI-compatible tool to get a ready-to-use request collection.
Error Handling
All error responses follow a unified format:
{
"success": false,
"error": "Invalid or expired token",
"errorCode": "token_invalid_or_expired"
}HTTP Status Codes
| Code | Description |
|---|---|
| 200 | Success |
| 201 | Created |
| 400 | Bad Request — invalid parameters |
| 401 | Unauthorized — missing or invalid token |
| 403 | Forbidden — insufficient permissions |
| 404 | Not Found |
| 429 | Rate Limited — too many requests |
| 500 | Internal Server Error |
Rate Limiting
API requests are rate-limited at three levels: IP-based, user-based, and API-key-based. When rate-limited, the API returns 429 with a Retry-After header indicating when to retry.
Rate limit response headers
Every rate-limited request (successful responses and 429s alike) carries these headers so clients can pace themselves:
| Header | Description |
|---|---|
| X-RateLimit-Limit | Token bucket capacity (burst) — the maximum requests you can make instantly from a full bucket. |
| X-RateLimit-Remaining | Requests remaining in the bucket after this request. |
| X-RateLimit-Reset | Unix seconds when the bucket refills to full capacity. |
| Retry-After | 429 responses only: suggested seconds to wait. |
Default quotas per auth mode
| Auth mode | Bucket | Default quota |
|---|---|---|
| Anonymous / temp inbox token | Per IP | 5 req/s · burst 20 |
| Signed-in user (JWT) | Per user (web requests get a 3× multiplier) | plan RPS × 3 · burst = RPS × 10 × 3 |
| API key | Per user + hot-path sub-buckets | plan RPS · burst = RPS × 10; plan daily/weekly/monthly API call quotas also apply |
Plan RPS values are listed per tier on the pricing page (e.g. Free 10 req/s, Basic 30 req/s, Pro 60 req/s); verified custom domains earn an extra rate bonus. Anonymous default is 5 req/s with burst 20. All values are operator-configurable — trust the live values in the response headers.
Quota & rate limits: quota exhaustion (quota_exhausted)
A 429 has two meanings: transient rate limiting (back off for Retry-After seconds and retry) and quota exhaustion. When your plan's daily/weekly/monthly API call quota is used up and no call-pack credits remain, further API requests are rejected immediately with errorCode: quota_exhausted, plus resetAt (quota reset time, UTC+8) and upgrade purchase links. Retrying before resetAt only yields more 429s — upgrade your plan or buy a call pack to resume immediately. The web console is never circuit-broken, so you can always sign in to purchase.
HTTP/1.1 429 Too Many Requests Retry-After: 28800 X-RateLimit-Reset: 1751731200 { "success": false, "error": "API 调用额度已用尽。购买调用包或升级套餐可立即恢复,也可等待额度自动重置。", "errorCode": "quota_exhausted", "resetAt": "2026-07-06T00: 00: 00+08: 00", "upgrade": { "pricing": "https://vip.215.im/pricing", "callPacks": "https://vip.215.im/balance" } }
Correct backoff: check errorCode first to tell the two 429 variants apart, and stop retrying on quota exhaustion.
resp = requests.get(url, headers=headers)
if resp.status_code == 429:
body = resp.json()
if body.get("errorCode") == "quota_exhausted":
# 额度用尽:重试无意义,等 resetAt 或购买调用包立即恢复
raise QuotaExhausted(body["resetAt"], body["upgrade"])
# 普通限速:按 Retry-After 退避后重试
time.sleep(int(resp.headers.get("Retry-After", "1")))