Email infrastructure built for AI agents

Donnez à un agent la capacité d'envoyer un email, d'authentifier un domaine, de suivre la remise et de diagnostiquer sa délivrabilité. Sans qu'un humain lise un tableau de bord entre deux étapes. Serveurs en Allemagne, envoi via Amazon SES Europe.

$ claude mcp add vaemail --env VAEMAIL_API_KEY=swm_... -- npx -y vaemail mcp
$ npx vaemail init
REST API MCP server CLI OpenAPI 3.1 Scoped keys Idempotency Dry-run Delivery status Audit log Webhooks Infrastructure européenne
agent · vaemail mcp

Ce parcours est réel. Déclaration du domaine, essai à blanc, envoi, relance après un délai réseau, statut de remise. La relance rend le même identifiant : le message n'est pas parti deux fois.

Un agent échoue autrement qu'une personne.

Il relance, il ne lit pas de tableau de bord, et il annonce une réussite parce qu'il a reçu un 200. L'API est faite pour ça.

Le piège le plus courant

Accepté n'est pas remis

Un envoi rend 202 et un identifiant : le message est en file, pas dans la boîte du destinataire. Seul le suivi dit ce qu'il est devenu, et les descriptions d'outils le disent noir sur blanc pour qu'un agent n'annonce pas une remise qu'il ne peut pas connaître.

GET /api/v1/messages/2841
"status": "delivered"
"events": [ delivered, opened ]
Idempotence

Relancer n'envoie pas deux fois

Un en-tête Idempotency-Key et la première réponse est rejouée pendant 24 heures au lieu de partir une seconde fois. La même clé avec un contenu différent est refusée : c'est une erreur d'appel, pas un rejeu.

Idempotency-Key: task-837-invoice
Idempotent-Replay: true
Erreurs

Chaque erreur dit quoi faire

Un code stable sur lequel brancher un comportement, la mention de ce qui peut être retenté, et l'action corrective avec l'endpoint qui la réalise. Pas de "Invalid request".

{
  "code": "DOMAIN_NOT_VERIFIED",
  "retryable": false,
  "resolution": {
    "action": "add_dns_records",
    "endpoint": "/api/v1/domains"
  }
}
Garde-fous

Une clé bornée ne déborde pas

Portées par clé, plafond d'envois par jour, nombre de destinataires par message, liste des domaines expéditeurs autorisés. Un agent ne peut pas engager plus que ce que sa clé permet, et la consommation restante se lit par API.

"scopes": ["email.send", "email.read_status"]
"max_emails_per_day": 100
"daily_remaining": 63
Journal

Autonome, mais redevable

Une ligne par appel : quelle clé, quelle opération, quels paramètres, quel résultat. Le corps des messages n'y figure jamais. C'est ce qui permet à un agent d'expliquer exactement ce qu'il a fait, et à un humain de le relire après coup.

Limite assumée

Les étapes humaines sont nommées

Publier un enregistrement DNS en est une. Un enregistrement dont la valeur dépend encore de la configuration du serveur d'envoi est marqué publishable: false : le poser tel quel casserait l'authentification du domaine. L'agent le signale au lieu de le faire.

Treize outils, et ce que chacun ne fait pas.

Le serveur MCP s'installe en une commande et n'a aucune dépendance. Node 18 suffit.

OutilCe qu'il fait
vaemail_capabilitiesCe que le service sait faire. Aucune clé nécessaire, c'est l'appel qui sert à décider si VaEmail couvre le besoin.
vaemail_send_emailMet un email en file et rend son identifiant. Accepte une clé d'idempotence.
vaemail_validate_emailEssai à blanc : dit si l'envoi passerait et nomme ce qui le bloquerait. N'envoie rien.
vaemail_get_messageStatut de remise d'un message et tous ses événements, y compris le motif d'échec.
vaemail_list_messagesMessages récents, filtrés par statut, étiquette ou destinataire.
vaemail_list_domainsDomaines d'envoi et état réel de leur SPF, DKIM et DMARC, lu dans le DNS public.
vaemail_create_domainDéclare un domaine et rend les enregistrements DNS à publier, avec le rôle de chacun.
vaemail_verify_domainRelit le DNS et dit ce qui est authentifié.
vaemail_dns_requirementsCe qui manque encore à un domaine déjà déclaré.
vaemail_diagnose_deliverabilityPourquoi le courrier arrive mal : authentification, rebonds, plaintes, provenance des contacts, et les actions qui corrigent.
vaemail_list_bouncesAdresses sorties du circuit et raison de chacune.
vaemail_get_usageQuota, plafond journalier de la clé, ce qu'il reste.
vaemail_get_audit_logCe que cette clé a fait, pour le rapporter sans le reconstituer de mémoire.

Trois façons de brancher, selon ce que fait votre agent.

MCP

Pour un agent conversationnel qui décide de ses outils. Claude Code, Claude Desktop, ou tout client MCP.

claude mcp add vaemail \
  --env VAEMAIL_API_KEY=swm_... \
  -- npx -y vaemail mcp

CLI

Pour un agent qui exécute des commandes, ou pour vérifier une configuration en dix secondes. --json sur n'importe quelle commande pour une sortie exploitable.

npx vaemail doctor
npx vaemail send --to a@b.fr \
  --subject Bonjour --html '<p>Salut</p>'

REST et OpenAPI

Pour du code. La spécification est générée depuis les routes réellement servies : elle ne peut pas décrire un endpoint disparu ni taire un endpoint récent.

curl -H "api-key: swm_..." \
  https://app.vaemail.fr/api/v1/capabilities

https://app.vaemail.fr/openapi.json

SMTP

Pour une application existante qu'on ne réécrit pas. L'API compatible Brevo répond aux mêmes chemins, avec le même schéma : changer l'URL de base suffit.

https://app.vaemail.fr/v3/smtp/email

Ce que VaEmail ne fait pas.

Une capacité annoncée à tort coûte plus cher qu'une capacité absente : l'agent construit dessus et échoue plus loin.

  • Pas de boîte de réception générique. On ne peut pas créer une adresse à la volée pour recevoir un code de vérification ou s'inscrire quelque part. Les réponses aux séquences de prospection, elles, sont bien relevées et on peut y répondre dans le fil.
  • Pas de création de compte par API. Ouvrir un compte et générer une première clé passe par un humain. Tout le reste s'enchaîne ensuite sans intervention.
  • Pas de publication DNS à votre place. Les enregistrements sont rendus prêts à poser, avec le rôle de chacun ; les publier reste une action chez votre registrar.
  • Pas de coût par message exposé en API. La consommation et les quotas se lisent, la facturation non.