Campagnes

Une campagne, c'est un e-mail, une audience, un départ. Tout ce qui précède l'envoi est réversible ; l'envoi lui-même ne l'est jamais.

Ce qu'est une campagne, côté HTTP

Une campagne, c'est un modèle, un objet et une cible : une liste, éventuellement restreinte à un tag. Elle choisit aussi qui la signe — l'une des adresses de votre carnet d'adresses d'expéditeur ou, à défaut, l'adresse par défaut de l'organisation.

Programmation : un champ facultatif, même endpoint

ChampOùObligatoireDescription
scheduledAtcorpsnonInstant ISO 8601 avec un décalage explicite. Omis, la campagne part maintenant
Pas de corps du tout, un corps vide ou un corps sans scheduledAt : tout cela signifie la même chose, envoyer maintenant. Les appelants écrits avant l'ajout de ce champ n'ont rien à changer.
bash
curl -X POST -H "x-api-key: $AGENTMAIL_API_KEY" -H "content-type: application/json" \
  -d '{"scheduledAt": "2026-08-12T07:00:00Z"}' \
  "https://www.agentsmail.io/api/v1/campaigns/$CAMPAIGN_ID/send"
json
{"success": true, "data": {"campaignId": "…", "status": "scheduled"}}
status distingue les deux cas : send_requested pour un envoi qui démarre maintenant, scheduled pour un envoi réservé pour plus tard. La campagne passe en scheduled, et GET /api/v1/campaigns/{campaignId} renvoie l'instant réellement enregistré — à relire une fois, car la programmation est conservée à la minute près et les secondes que vous transmettez sont ignorées.
Une date sans fuseau horaire est refusée, jamais interprétée comme UTC. 2026-08-12T09:00:00 répond 400 : l'API ne devine pas un décalage que vous n'avez pas écrit, car une erreur de devinette enverrait avec une heure ou deux d'écart sans que rien dans la requête ne le montre. Écrivez 2026-08-12T07:00:00Z, ou 2026-08-12T09:00:00+02:00 — les deux désignent le même instant. Un instant passé, ou à moins de quelques minutes, est refusé de la même façon.
La programmation concerne un draft, et la pause/reprise restent dans l'application. Une campagne déjà scheduled répond 400 — elle n'est pas reprogrammée sur place. Mettre en pause ou reprendre une programmation se fait depuis l'application, sur l'écran de la campagne ; aucun endpoint ne l'expose. Réservez l'heure que vous voulez vraiment.
État de la campagnePOST /sendavec scheduledAt
draft202 — l'envoi est demandé202 — la campagne est programmée
scheduled202 — elle part maintenant400 — seul un brouillon peut être programmé
paused409 — reprenez-la dans l'application409
sending409 — un envoi est déjà en cours409
sent409 — elle est déjà partie409
failed409 — pas dans un état permettant l'envoi409
aucun lien de désabonnement dans le contenu400 — rien n'est émis400 — rien n'est programmé
inconnue, ou d'une autre organisation404404
Le contenu est rendu avant toute émission. C'est exactement la même vérification que POST /api/v1/render : une campagne dont le HTML ne porte pas {{unsubscribeUrl}} répond 400 et aucun envoi n'est créé. Sans ce lien, chaque envoi échouerait plus tard pour une raison que l'API ne vous montre jamais. Corrigez le modèle, recréez la campagne, puis envoyez.
Appeler l'envoi deux fois n'envoie jamais deux fois. Une campagne déjà partie répond 409 et aucun second envoi n'est lancé — réessayer après un délai d'attente dépassé est sans risque. Pour renvoyer le même contenu, créez une nouvelle campagne.
Interrogez ensuite GET /api/v1/campaigns/{campaignId} : status passe de draft → sending → sent, et progress se remplit au fil de l'envoi. Il n'y a aucun webhook à attendre.

Quand l'interrogation ne converge jamais

Un 202 signifie que la requête a été acceptée, pas que l'envoi aura lieu. Deux conditions l'arrêtent après coup, et aucune ne modifie la campagne : elle reste draft, progress reste à zéro, et aucune erreur ne vous est jamais signalée.
Ce que vous voyezCe que cela signifieQue faire
draft, progress.total = 0, sendingEnabled = falseL'envoi en masse est coupé sur tout le compte (arrêt d'urgence)Rien de votre côté — l'envoi doit être réactivé
draft, progress.total = 0, sendingEnabled = trueLe recipientCount de cette campagne dépasserait le quota d'envoi sur 24 hAttendez que la fenêtre du quota glisse, puis rappelez /send
Arrêtez d'interroger si la campagne est toujours draft sans progression. Elle ne passera jamais en sent d'elle-même. Lisez sendingEnabled : false signifie l'arrêt d'urgence, true le quota quotidien — dans les deux cas la campagne est intacte et rappeler /send plus tard est sans risque, c'est toujours un draft.

Une campagne à partir de rien, de bout en bout

1

Écrire le modèle

bash
TEMPLATE_ID=$(curl -s -X POST -H "x-api-key: $AGENTMAIL_API_KEY" -H "content-type: application/json" \
  -d '{
    "name": "August newsletter",
    "defaultSubject": "What shipped in August",
    "content": "<html><body><p>Hi {{firstName}}</p><p>Here is what we shipped.</p><a href=\"{{unsubscribeUrl}}\">Unsubscribe</a></body></html>"
  }' \
  "https://www.agentsmail.io/api/v1/templates" | jq -r '.data.id')
Vérifiez unsupportedTags dans la réponse : tout ce qui y figure est encore brut dans votre HTML.
2

Le rendre, et lire ce que vous obtenez

bash
curl -s -X POST -H "x-api-key: $AGENTMAIL_API_KEY" -H "content-type: application/json" \
  -d '{"templateId":"'$TEMPLATE_ID'","variables":{"firstName":"Camille"}}' \
  "https://www.agentsmail.io/api/v1/render" | jq '{subject, unknownVariables: .data.unknownVariables}'
Un 400 à cette étape signifie que le HTML n'a pas de lien de désabonnement. Corrigez le modèle avec PATCH et relancez le rendu — cette boucle ne coûte rien.
3

Choisir la cible

bash
LIST_ID=$(curl -s -H "x-api-key: $AGENTMAIL_API_KEY" \
  "https://www.agentsmail.io/api/v1/lists" | jq -r '.data[0].id')

curl -s -H "x-api-key: $AGENTMAIL_API_KEY" \
  "https://www.agentsmail.io/api/v1/tags" | jq '.data[] | {id, name, contactCount}'
Les ids de liste viennent de GET /api/v1/lists — voir Découverte. Les tags viennent de l'API Audience ; gardez l'id de celui que vous voulez, le cas échéant.
4

Créer la campagne

bash
CAMPAIGN_ID=$(curl -s -X POST -H "x-api-key: $AGENTMAIL_API_KEY" -H "content-type: application/json" \
  -d '{
    "name": "August newsletter",
    "subject": "What shipped in August",
    "templateId": "'$TEMPLATE_ID'",
    "listId": "'$LIST_ID'"
  }' \
  "https://www.agentsmail.io/api/v1/campaigns" | jq -r '.data.id')
5

Compter les destinataires avant de s'engager

bash
curl -s -H "x-api-key: $AGENTMAIL_API_KEY" \
  "https://www.agentsmail.io/api/v1/campaigns/$CAMPAIGN_ID" | jq '.data.recipientCount'
Zéro signifie que la cible ne correspond à personne. L'envoi ne ferait rien — corrigez d'abord la cible.
6

Envoyer

bash
curl -s -X POST -H "x-api-key: $AGENTMAIL_API_KEY" \
  "https://www.agentsmail.io/api/v1/campaigns/$CAMPAIGN_ID/send"
202 signifie que l'envoi a été accepté, pas qu'il est terminé.
7

Le suivre

bash
curl -s -H "x-api-key: $AGENTMAIL_API_KEY" \
  "https://www.agentsmail.io/api/v1/campaigns/$CAMPAIGN_ID" | jq '{status: .data.status, progress: .data.progress}'
Interrogez jusqu'à ce que status soit sent. Ne rappelez pas /send pendant l'attente — il répond 409. Si la campagne est toujours draft avec progress.total à zéro, arrêtez d'interroger et lisez Quand l'interrogation ne converge jamais plus haut : l'envoi est coupé, ou le quota quotidien est atteint.