Tout ce qu'un client MCP fait, un script peut le faire en HTTPS ordinaire. Un endpoint, une clé API, du JSON en entrée et du JSON en sortie. Ce guide couvre l'authentification, les trois appels dont vous avez besoin, les erreurs, les nouvelles tentatives, les limites et les webhooks pour le nouveau courrier.
Chaque requête est un POST JSON-RPC 2.0 vers https://mcpemails.com/api/mcp. C'est le même endpoint que celui qu'utilisent claude.ai, ChatGPT et les autres clients MCP, avec les mêmes outils et les mêmes permissions : un script et un assistant de chat voient un seul et même produit.
Content-Type: application/json et Accept: application/json. La réponse est un seul objet JSON.Authorization.structuredContent typé, vous n'avez donc jamais à analyser de la prose. Les mêmes données sont dans content[0].text sous forme de chaîne JSON.mcpe_ suivi de 64 caractères hexadécimaux. Elle est affichée une seule fois, à sa création, et seul un hachage est conservé. Copiez-la à ce moment-là ; une clé perdue ne se récupère pas, elle se remplace.Authorization: Bearer mcpe_.... Ne mettez jamais une clé dans une URL ou une query string.Chaque clé ne détient que les permissions que vous cochez à sa création. Vous pouvez les modifier plus tard avec Modifier l'accès, et le changement s'applique dès l'appel suivant, sans reconnexion.
| Portée | Autorise |
|---|---|
read:email | Lister, lire et rechercher des messages, télécharger des pièces jointes, lister les boîtes et les dossiers. |
search:email | Conservée pour les anciennes clés. La recherche est couverte par read:email. |
send:email | Envoyer, répondre et transférer. |
manage:drafts | Créer, modifier et supprimer des brouillons. Envoyer un brouillon demande aussi send:email. |
manage:folders | Déplacer, copier, marquer et archiver des messages ; créer, renommer et supprimer des dossiers. |
delete:email | Supprimer des messages, un par un ou en masse. |
manage:contacts | Rechercher des contacts dans votre courrier récent. |
schedule:email | Programmer des envois et gérer les e-mails programmés. |
manage:automations | Créer des règles qui s'exécutent selon un calendrier sans que personne ne surveille. |
Commencez avec read:email et n'ajoutez une portée d'écriture que lorsque le workflow en a besoin. Un appel hors des portées de la clé est refusé avant que nous contactions votre serveur de messagerie. Les lecteurs d'un espace de travail ne peuvent détenir que des portées de lecture.
Par défaut, une clé atteint toutes les boîtes de l'espace de travail. Pour la restreindre à certaines d'entre elles, ouvrez Modifier l'accès sur la clé et choisissez les boîtes sous Accès aux boîtes. La restriction est vérifiée à chaque appel : une clé limitée à la boîte support ne peut pas lire celle de la comptabilité, quoi que dise la requête.
Les applications que les gens connectent eux-mêmes, comme claude.ai, ChatGPT et les autres clients MCP, se connectent avec OAuth plutôt qu'avec une clé : enregistrement dynamique du client, code d'autorisation avec PKCE et un écran de consentement où l'utilisateur choisit les portées et les boîtes. Voir Connexion OAuth. Pour vos propres scripts, serveurs et workflows, une clé API est plus simple.
Les clients MCP commencent par initialize. Un script peut s'en passer, puisque le serveur ne garde aucune session, mais c'est un moyen peu coûteux de vérifier que la clé fonctionne et de lire les instructions du serveur.
curl -s https://mcpemails.com/api/mcp \
-H "Authorization: Bearer $MCPEMAILS_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"clientInfo": { "name": "my-agent", "version": "1.0" },
"capabilities": {}
}
}'tools/list renvoie chaque outil que cette clé peut appeler, avec sa description, son schéma d'entrée et son schéma de sortie. Lisez-le au démarrage plutôt que de coder en dur des noms d'outils ou des arguments : la liste est filtrée selon les portées de la clé, et les outils gagnent des arguments au fil du temps.
curl -s https://mcpemails.com/api/mcp \
-H "Authorization: Bearer $MCPEMAILS_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }'Appelez un outil par son nom avec ses arguments. La plupart des outils prennent une action qui choisit ce qu'ils font, comme list, read ou search sur email_read. Désignez la boîte avec inbox (son adresse) ou inbox_id ; inbox_list renvoie les deux.
curl -s https://mcpemails.com/api/mcp \
-H "Authorization: Bearer $MCPEMAILS_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "email_read",
"arguments": { "action": "list", "inbox": "you@example.com", "unread": true, "limit": 20 }
}
}'Un résultat réussi :
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [{ "type": "text", "text": "{\"messages\":[...],\"total\":42, ...}" }],
"structuredContent": {
"messages": [{ "id": "...", "subject": "...", "from": { "name": "...", "email": "..." } }],
"total": 42,
"total_is_estimate": false,
"has_more": true,
"next_offset": 20
}
}
}La référence des outils décrit chaque outil, et le guide curl contient d'autres exemples.
Il en existe deux sortes. Une requête qui n'a pas pu s'exécuter du tout revient comme une error JSON-RPC avec un statut HTTP. Une requête qui a atteint un outil et a été refusée par lui revient comme un result normal avec isError: true, pour qu'un modèle d'IA lise la raison. Branchez sur data.error_code ou sur le code _meta, pas sur le texte du message.
| Réponse | Quand | Que faire |
|---|---|---|
HTTP 401, -32001 | Clé absente, malformée, révoquée ou expirée. | Corrigez l'en-tête Authorization ou créez une nouvelle clé. Réessayer ne servira à rien. |
HTTP 403, -32004, insufficient_scope | La clé n'a pas une portée dont cette action d'outil a besoin. data.required_scopes liste ce qui l'autoriserait. | Ajoutez la portée avec Modifier l'accès, ou utilisez une autre clé. |
HTTP 429, -32003, rate_limit_exceeded | Trop de requêtes. data.window indique quelle fenêtre est saturée. | Attendez Retry-After secondes (aussi dans data.retry_after), puis renvoyez la même requête. |
-32602 | Nom d'outil inconnu. data.available_tools liste les noms que cette clé peut appeler. | Utilisez un nom tiré de tools/list. |
isError: true, invalid_arguments | Les arguments n'ont pas passé le schéma de l'outil. Rien n'a été exécuté. Le texte nomme chaque argument fautif et, quand un nom d'argument a été deviné, indique celui qui existe. | Corrigez les arguments. Les mêmes arguments sont refusés de la même manière. |
isError: true, usage_limit_reached | Un espace de travail Gratuit a épuisé ses actions e-mail du mois. | Attendez la date de réinitialisation dans _meta, ou passez à une formule supérieure. Les formules payantes n'ont pas de plafond mensuel. |
isError: true, autre | L'outil s'est exécuté et a échoué : message introuvable, refus du fournisseur, destinataire invalide, etc. | Lisez content[0].text. Il dit ce qui s'est passé et si quelque chose a été envoyé. |
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope", ...
{
"jsonrpc": "2.0",
"id": 4,
"error": {
"code": -32004,
"message": "Insufficient scope: the 'send:email' scope is required to call email_compose.",
"data": { "error_code": "insufficient_scope", "required_scopes": ["send:email"], ... }
}
}HTTP/1.1 429 Too Many Requests
Retry-After: 12
{
"jsonrpc": "2.0",
"id": 5,
"error": {
"code": -32003,
"message": "Rate limit exceeded",
"data": { "error_code": "rate_limit_exceeded", "window": "per_minute", "retry_after": 12, ... }
}
}{
"jsonrpc": "2.0",
"id": 6,
"result": {
"isError": true,
"content": [{ "type": "text", "text": "Invalid arguments for email_read. The call was rejected before it ran, ..." }],
"_meta": {
"com.mcpemails/invalid_arguments": {
"error_code": "invalid_arguments",
"retryable": false,
"errors": [{ "path": "arguments.since", "message": "..." }]
}
}
}
}Chaque résultat de liste ou de recherche se termine par les mêmes quatre champs :
has_more : true quand une autre page existe. Seul has_more: false signifie que vous avez tout vu.next_offset : renvoyez-le comme offset, avec tous les autres arguments inchangés, pour obtenir la page suivante. Il vaut null quand has_more est false.total : le nombre de correspondances, ou null quand le fournisseur ne peut pas les compter. Il ne contredit jamais la page que vous tenez.total_is_estimate : true quand total est une estimation ou un minimum plutôt qu'un décompte exact.Les corps de message longs sont découpés de la même façon : quand body_truncated est true, relisez avec body_next_offset comme body_offset.
Une connexion coupée après un envoi vous laisse sans savoir si le courrier est parti. Passez une idempotency_key et réessayez avec la même clé : le travail ne s'exécute qu'une fois.
curl -s https://mcpemails.com/api/mcp \
-H "Authorization: Bearer $MCPEMAILS_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 7,
"method": "tools/call",
"params": {
"name": "email_compose",
"arguments": {
"action": "send",
"inbox": "you@example.com",
"to": ["customer@example.com"],
"subject": "Your order has shipped",
"body": "Hi, your order is on its way.",
"idempotency_key": "order-1042-shipped"
}
}
}'tools/list affiche idempotency_key dans le schéma d'entrée de chaque outil qui l'accepte.idempotent_replay: true, le status d'origine et le résultat d'origine dans result, y compris le message_id que vous avez peut-être perdu.idempotency_key_conflict. Utilisez une nouvelle clé pour une nouvelle opération.status: "processing". Réessayez peu après.status: "unknown". Vérifiez les éléments envoyés avant de renvoyer sous une nouvelle clé.{
"idempotency_key": "order-1042-shipped",
"idempotent_replay": true,
"status": "succeeded",
"result": { "message_id": "...", "inbox_id": "...", "status": "sent" },
"message": "This logical request was already processed. ..."
}N'importe quelle boîte peut retenir chaque envoi jusqu'à ce qu'une personne l'approuve. Un envoi depuis votre script renvoie alors un approval_id avec status: "pending_approval" au lieu de partir, et il part quand quelqu'un l'approuve dans le tableau de bord. Cela fonctionne sur toutes les formules et constitue un bon réglage par défaut le temps qu'une nouvelle automatisation fasse ses preuves. Une nouvelle tentative avec la même idempotency_key signale l'approbation au lieu d'en créer une seconde.
Chaque formule a un plafond par minute pour l'ensemble de l'espace de travail. Une seule clé peut utiliser toute la limite par minute de la formule ; plusieurs clés la partagent.
| Formule | Requêtes par minute | Webhooks |
|---|---|---|
| Free | 60 | Non |
| Personal | 120 | Non |
| Pro | 300 | Oui |
| Business | 1 000 | Oui |
tools/call portent RateLimit-Limit, RateLimit-Remaining et RateLimit-Reset (en secondes) pour la fenêtre la plus proche de la saturation, afin qu'un client puisse régler son rythme avant d'atteindre la limite.data.window.Retry-After en secondes. Attendez ce délai, puis renvoyez la même requête.Sur Pro et Business, MCPEmails peut envoyer un POST à votre URL quand du courrier arrive dans une boîte, pour qu'un workflow démarre sur le nouveau courrier au lieu d'interroger le serveur. Créez un endpoint dans le tableau de bord, choisissez ses boîtes et copiez le secret de signature, affiché une seule fois. L'URL doit être en HTTPS, sur le port 443 ou 8443. Utilisez le bouton de test pour envoyer un événement ping signé.
POST /your/webhook HTTP/1.1
Content-Type: application/json
MCPEmails-Event-Id: evt_...
MCPEmails-Event-Type: message.received
MCPEmails-Signature: t=1792843200,v1=5f2b...
{
"id": "evt_...",
"type": "message.received",
"created_at": "2026-10-24T09:20:00.000Z",
"workspace_id": "...",
"inbox_id": "...",
"folder": "INBOX",
"count": 1,
"message_ids": ["..."]
}type vaut message.received. message_ids liste les nouveaux messages du dossier INBOX de cette boîte, jusqu'à 10 par événement ; count indique combien sont arrivés, ce qui peut être plus.messages avec id, from et subject de chaque message.email_read (action: "read", l'inbox_id et un message_id) en utilisant une clé API. Les portées et l'accès aux boîtes de cette clé s'appliquent comme d'habitude.Chaque livraison porte MCPEmails-Signature: t=<secondes unix>,v1=<hex>, où v1 est le HMAC-SHA256 de l'horodatage, d'un point et du corps brut de la requête, avec le secret de l'endpoint comme clé. La clé est le secret entier tel qu'affiché, préfixe whsec_ inclus. Recalculez la signature sur le corps brut, comparez en temps constant et rejetez tout horodatage de plus de cinq minutes.
import crypto from 'node:crypto';
// rawBody: the request body exactly as received, before JSON.parse.
export function verifyMcpemailsSignature(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(
String(header ?? '').split(',').map((part) => part.trim().split('=')),
);
const timestamp = Number(parts.t);
if (!Number.isFinite(timestamp)) return false;
if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${parts.t}.${rawBody}`)
.digest();
const received = Buffer.from(parts.v1 ?? '', 'hex');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}MCPEmails-Event-Id pour ignorer une livraison déjà traitée. Une nouvelle tentative porte le même identifiant d'événement.POST, et enregistrez son URL de production comme endpoint de webhook dans le tableau de bord MCPEmails.POST, URL https://mcpemails.com/api/mcp, authentification Generic Credential Type → Header Auth avec le nom Authorization et la valeur Bearer mcpe_....result.structuredContent.NODE_FUNCTION_ALLOW_BUILTIN=crypto.{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "email_read",
"arguments": {
"action": "read",
"inbox_id": "{{ $json.body.inbox_id }}",
"message_id": "{{ $json.body.message_ids[0] }}"
}
}
}Pour un nœud AI Agent qui lit et envoie lui-même du courrier, voir le guide n8n.
Ajoutez le serveur avec une clé dans un en-tête. Cela convient aux machines sans interface et à la CI, où il n'y a pas de navigateur pour la connexion OAuth :
claude mcp add --transport http mcpemails https://mcpemails.com/api/mcp \
--header "Authorization: Bearer $MCPEMAILS_KEY"Le guide Claude Code couvre la connexion OAuth et le choix des portées.
Liste chaque message non lu d'une boîte, page par page, et patiente le temps d'un 429 :
// node agent.mjs (Node 18 or later, MCPEMAILS_KEY in the environment)
const ENDPOINT = 'https://mcpemails.com/api/mcp';
let nextId = 1;
async function callTool(name, args) {
const res = await fetch(ENDPOINT, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.MCPEMAILS_KEY}`,
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify({ jsonrpc: '2.0', id: nextId++, method: 'tools/call', params: { name, arguments: args } }),
});
if (res.status === 429) {
const wait = Number(res.headers.get('Retry-After') ?? 1);
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
return callTool(name, args);
}
const reply = await res.json();
if (reply.error) throw new Error(`${reply.error.code}: ${reply.error.message}`);
if (reply.result.isError) throw new Error(reply.result.content[0].text);
return reply.result.structuredContent;
}
const inbox = 'you@example.com';
let offset = 0;
do {
const page = await callTool('email_read', { action: 'list', inbox, unread: true, limit: 50, offset });
for (const m of page.messages) console.log(m.id, m.from?.email, m.subject);
offset = page.has_more ? page.next_offset : null;
} while (offset !== null);OpenClaw se connecte avec OAuth par défaut ; le guide OpenClaw le détaille. Pour utiliser plutôt une clé à portées limitées, placez-la dans la map headers de l'entrée du serveur et laissez auth non défini, car OpenClaw ignore un en-tête Authorization statique sur une entrée réglée en OAuth.
{
"mcp": {
"servers": {
"mcpemails": {
"url": "https://mcpemails.com/api/mcp",
"transport": "streamable-http",
"headers": { "Authorization": "Bearer mcpe_YOUR_KEY_HERE" }
}
}
}
}Créez une clé, appelez tools/list et regardez votre propre courrier revenir en JSON.