Install
openclaw skills install zoho-mailZoho Mail API integration with managed OAuth. Send, receive, and manage emails, folders, and labels. Use this skill when users want to send emails, read messages, manage folders, or work with email labels in Zoho Mail. For other third party apps, use the api-gateway skill (https://clawhub.ai/byungkyu/api-gateway). Requires network access and valid Maton API key.
openclaw skills install zoho-mailAccess the Zoho Mail API with managed OAuth authentication. Send, receive, search, and manage emails with full folder and label management.
# List all accounts
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/zoho-mail/api/accounts')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
https://api.maton.ai/zoho-mail/{native-api-path}
Maton proxies requests to mail.zoho.com and automatically injects your OAuth token.
All requests require the Maton API key in the Authorization header:
Authorization: Bearer $MATON_API_KEY
Environment Variable: Set your API key as MATON_API_KEY:
export MATON_API_KEY="YOUR_API_KEY"
Manage your Zoho Mail OAuth connections at https://api.maton.ai.
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections?app=zoho-mail&status=ACTIVE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
python <<'EOF'
import urllib.request, os, json
data = json.dumps({'app': 'zoho-mail'}).encode()
req = urllib.request.Request('https://api.maton.ai/connections', data=data, method='POST')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Content-Type', 'application/json')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections/{connection_id}')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Response:
{
"connection": {
"connection_id": "{connection_id}",
"status": "ACTIVE",
"creation_time": "2025-12-08T07:20:53.488460Z",
"last_updated_time": "2026-01-31T20:03:32.593153Z",
"url": "https://connect.maton.ai/?session_token=...",
"app": "zoho-mail",
"metadata": {}
}
}
Open the returned url in a browser to complete OAuth authorization.
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections/{connection_id}', method='DELETE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
If you have multiple Zoho Mail connections, specify which one to use with the Maton-Connection header:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/zoho-mail/api/accounts')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Maton-Connection', '{connection_id}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
If you have multiple connections, always include this header to ensure requests go to the intended account.
Retrieve all mail accounts for the authenticated user.
GET /zoho-mail/api/accounts
Example:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/zoho-mail/api/accounts')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
GET /zoho-mail/api/accounts/{accountId}
GET /zoho-mail/api/accounts/{accountId}/folders
Example:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/zoho-mail/api/accounts/{accountId}/folders')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Response:
{
"status": {
"code": 200,
"description": "success"
},
"data": [
{
"folderId": "1367000000000008014",
"folderName": "Inbox",
"folderType": "Inbox",
"path": "/Inbox",
"imapAccess": true,
"isArchived": 0,
"URI": "https://mail.zoho.com/api/accounts/1367000000000008002/folders/1367000000000008014"
},
{
"folderId": "1367000000000008016",
"folderName": "Drafts",
"folderType": "Drafts",
"path": "/Drafts",
"imapAccess": true,
"isArchived": 0
}
]
}
POST /zoho-mail/api/accounts/{accountId}/folders
Content-Type: application/json
{
"folderName": "My Custom Folder"
}
PUT /zoho-mail/api/accounts/{accountId}/folders/{folderId}
Content-Type: application/json
{
"folderName": "Renamed Folder"
}
DELETE /zoho-mail/api/accounts/{accountId}/folders/{folderId}
GET /zoho-mail/api/accounts/{accountId}/labels
Example:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/zoho-mail/api/accounts/{accountId}/labels')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
POST /zoho-mail/api/accounts/{accountId}/labels
Content-Type: application/json
{
"labelName": "Important"
}
PUT /zoho-mail/api/accounts/{accountId}/labels/{labelId}
Content-Type: application/json
{
"labelName": "Updated Label"
}
DELETE /zoho-mail/api/accounts/{accountId}/labels/{labelId}
GET /zoho-mail/api/accounts/{accountId}/messages/view?folderId={folderId}
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
folderId | long | Folder ID to list messages from |
limit | integer | Number of messages to return (default: 50) |
start | integer | Offset for pagination |
sortBy | string | Sort field (e.g., date) |
sortOrder | boolean | true for ascending, false for descending |
Example:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/zoho-mail/api/accounts/{accountId}/messages/view?folderId={folderId}&limit=10')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
GET /zoho-mail/api/accounts/{accountId}/messages/search?searchKey={query}
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
searchKey | string | Search query |
limit | integer | Number of results to return |
start | integer | Offset for pagination |
Example:
python <<'EOF'
import urllib.request, os, json
import urllib.parse
query = urllib.parse.quote('from:sender@example.com')
req = urllib.request.Request(f'https://api.maton.ai/zoho-mail/api/accounts/{{accountId}}/messages/search?searchKey={query}')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
GET /zoho-mail/api/accounts/{accountId}/folders/{folderId}/messages/{messageId}/content
Example:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/zoho-mail/api/accounts/{accountId}/folders/{folderId}/messages/{messageId}/content')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
GET /zoho-mail/api/accounts/{accountId}/folders/{folderId}/messages/{messageId}/header
GET /zoho-mail/api/accounts/{accountId}/folders/{folderId}/messages/{messageId}/details
GET /zoho-mail/api/accounts/{accountId}/messages/{messageId}/originalmessage
POST /zoho-mail/api/accounts/{accountId}/messages
Content-Type: application/json
{
"fromAddress": "sender@yourdomain.com",
"toAddress": "recipient@example.com",
"subject": "Email Subject",
"content": "Email body content",
"mailFormat": "html"
}
Request Body Fields:
| Field | Type | Required | Description |
|---|---|---|---|
fromAddress | string | Yes | Sender's email address |
toAddress | string | Yes | Recipient's email address |
subject | string | Yes | Email subject |
content | string | Yes | Email body content |
ccAddress | string | No | CC recipient |
bccAddress | string | No | BCC recipient |
mailFormat | string | No | html or plaintext (default: html) |
askReceipt | string | No | yes or no for read receipt |
encoding | string | No | Character encoding (default: UTF-8) |
Example - Send Email:
python <<'EOF'
import urllib.request, os, json
data = json.dumps({
"fromAddress": "sender@yourdomain.com",
"toAddress": "recipient@example.com",
"subject": "Hello from Zoho Mail API",
"content": "<h1>Hello!</h1><p>This is a test email.</p>",
"mailFormat": "html"
}).encode()
req = urllib.request.Request('https://api.maton.ai/zoho-mail/api/accounts/{accountId}/messages', data=data, method='POST')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Content-Type', 'application/json')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Scheduling Parameters (Optional):
| Field | Type | Description |
|---|---|---|
isSchedule | boolean | Enable scheduling |
scheduleType | integer | 1-5 for preset times; 6 for custom |
timeZone | string | Required if scheduleType=6 (e.g., GMT 5:30) |
scheduleTime | string | Required if scheduleType=6 (format: MM/DD/YYYY HH:MM:SS) |
POST /zoho-mail/api/accounts/{accountId}/messages/{messageId}
Content-Type: application/json
{
"fromAddress": "sender@yourdomain.com",
"toAddress": "recipient@example.com",
"subject": "Re: Original Subject",
"content": "Reply content"
}
POST /zoho-mail/api/accounts/{accountId}/messages
Content-Type: application/json
{
"fromAddress": "sender@yourdomain.com",
"toAddress": "recipient@example.com",
"subject": "Draft Subject",
"content": "Draft content",
"mode": "draft"
}
PUT /zoho-mail/api/accounts/{accountId}/updatemessage
Content-Type: application/json
{
"messageId": ["messageId1", "messageId2"],
"folderId": "folderId",
"mode": "markAsRead"
}
Mode Options:
markAsRead - Mark messages as readmarkAsUnread - Mark messages as unreadmoveMessage - Move messages (requires destfolderId)setFlag - Set flag (requires flagid)applyLabel - Apply labels (requires labelId)archive - Archive messagesunArchive - Unarchive messagesspam - Mark as spamnotSpam - Mark as not spamExample - Mark as Read:
python <<'EOF'
import urllib.request, os, json
data = json.dumps({
"messageId": ["1234567890123456789"],
"folderId": "9876543210987654321",
"mode": "markAsRead"
}).encode()
req = urllib.request.Request('https://api.maton.ai/zoho-mail/api/accounts/{accountId}/updatemessage', data=data, method='PUT')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Content-Type', 'application/json')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Flag messages with a color/status indicator.
PUT /zoho-mail/api/accounts/{accountId}/updatemessage
Content-Type: application/json
{
"mode": "setFlag",
"messageId": ["messageId1", "messageId2"],
"flagid": "important"
}
Flag ID Options:
| Flag ID | Description |
|---|---|
info | Info flag (blue) |
important | Important flag (red) |
followup | Follow-up flag (orange) |
flag_not_set | Remove flag |
Optional Parameters:
threadId - Array of thread IDs (alternative to messageId)isFolderSpecific - Set to true if using folderIdfolderId - Folder ID (required if isFolderSpecific is true)isArchive - Set to true to include archived emailsExample - Flag as Important:
python <<'EOF'
import urllib.request, os, json
data = json.dumps({
"mode": "setFlag",
"messageId": ["1234567890123456789"],
"flagid": "important",
"isFolderSpecific": True,
"folderId": "9876543210987654321"
}).encode()
req = urllib.request.Request('https://api.maton.ai/zoho-mail/api/accounts/{accountId}/updatemessage', data=data, method='PUT')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Content-Type', 'application/json')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Apply one or more labels to messages or threads.
PUT /zoho-mail/api/accounts/{accountId}/updatemessage
Content-Type: application/json
{
"mode": "applyLabel",
"messageId": ["messageId1"],
"labelId": ["labelId1", "labelId2"]
}
Required Parameters:
mode - Must be "applyLabel"messageId or threadId - Array of message/thread IDslabelId - Array of label IDs to applyOptional Parameters:
isFolderSpecific - Set to true if using folderIdfolderId - Folder ID (required if isFolderSpecific is true)isArchive - Set to true to include archived emailsExample - Apply Labels:
python <<'EOF'
import urllib.request, os, json
data = json.dumps({
"mode": "applyLabel",
"messageId": ["1234567890123456789"],
"labelId": ["111222333444555666", "777888999000111222"],
"isFolderSpecific": True,
"folderId": "9876543210987654321"
}).encode()
req = urllib.request.Request('https://api.maton.ai/zoho-mail/api/accounts/{accountId}/updatemessage', data=data, method='PUT')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Content-Type', 'application/json')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Note: Get label IDs by calling GET /zoho-mail/api/accounts/{accountId}/labels first.
DELETE /zoho-mail/api/accounts/{accountId}/folders/{folderId}/messages/{messageId}
POST /zoho-mail/api/accounts/{accountId}/messages/attachments
Content-Type: multipart/form-data
GET /zoho-mail/api/accounts/{accountId}/folders/{folderId}/messages/{messageId}/attachmentinfo
GET /zoho-mail/api/accounts/{accountId}/folders/{folderId}/messages/{messageId}/attachments/{attachmentId}
Zoho Mail uses offset-based pagination:
GET /zoho-mail/api/accounts/{accountId}/messages/view?folderId={folderId}&start=0&limit=50
start: Offset index (default: 0)limit: Number of records to return (default: 50)For subsequent pages, increment start by limit:
start=0&limit=50start=50&limit=50start=100&limit=50const response = await fetch(
'https://api.maton.ai/zoho-mail/api/accounts',
{
headers: {
'Authorization': `Bearer ${process.env.MATON_API_KEY}`
}
}
);
const data = await response.json();
import os
import requests
response = requests.get(
'https://api.maton.ai/zoho-mail/api/accounts',
headers={'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}'}
)
data = response.json()
/api/accounts to get your account IDfromAddress must be associated with the authenticated accountINVALID_OAUTHSCOPE error, contact Maton support at support@maton.ai with the specific operations/APIs you need and your use-casecurl -g when URLs contain brackets to disable glob parsingjq or other commands, environment variables like $MATON_API_KEY may not expand correctly in some shell environments| Status | Meaning |
|---|---|
| 400 | Missing Zoho Mail connection or invalid request |
| 401 | Invalid or missing Maton API key |
| 429 | Rate limited |
| 4xx/5xx | Passthrough error from Zoho Mail API |
MATON_API_KEY environment variable is set:echo $MATON_API_KEY
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
zoho-mail. For example:https://api.maton.ai/zoho-mail/api/accountshttps://api.maton.ai/api/accounts