POSTCreate a campaign/api/v1/campaignsGETList campaigns/api/v1/campaignsGETGet a campaign/api/v1/campaigns/{campaignId}PATCHEdit a draft campaign/api/v1/campaigns/{campaignId}POSTSend or schedule/api/v1/campaigns/{campaignId}/sendPOSTTest a campaign/api/v1/campaigns/{campaignId}/test-sendGETRead campaign stats/api/v1/campaigns/{campaignId}/stats
Qué es una campaña, vista por HTTP
Una campaña es una plantilla, un asunto y un objetivo: una lista, opcionalmente acotada a una etiqueta. También elige quién la firma: una de las direcciones de tu libreta de remitentes o, por defecto, la dirección predeterminada de la organización.Programación: un campo opcional, el mismo endpoint
| Campo | Dónde | Obligatorio | Descripción |
|---|---|---|---|
scheduledAt | cuerpo | no | Instante ISO 8601 con un desfase explícito. Si se omite, la campaña sale ya |
scheduledAt, todo significa lo mismo: enviar ahora. Las llamadas escritas antes de que existiera este campo no tienen nada que cambiar.
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 los dos casos: send_requested para un envío que empieza ahora, scheduled para uno reservado para más tarde. La campaña pasa a scheduled, y GET /api/v1/campaigns/{campaignId} devuelve el instante realmente guardado; vale la pena leerlo una vez, porque la programación se guarda al minuto y los segundos que envíes se descartan.
Una fecha sin zona horaria se rechaza; nunca se interpreta como UTC.
2026-08-12T09:00:00 responde 400: la API no adivina un desfase que no escribiste, porque adivinarlo mal envía con una o dos horas de diferencia sin que nada en la solicitud lo muestre. Escribe 2026-08-12T07:00:00Z o 2026-08-12T09:00:00+02:00: ambos designan el mismo instante. Un instante en el pasado, o a menos de un par de minutos, se rechaza de la misma forma.La programación es para un
draft, y pausar/reanudar se hace en la app. Una campaña que ya está scheduled responde 400: no se reprograma sobre la marcha. Pausar o reanudar una programación se hace desde la app, en la pantalla de la campaña; ningún endpoint lo expone. Reserva la hora que realmente quieres.| Estado de la campaña | POST /send | con scheduledAt |
|---|---|---|
draft | 202: se solicita el envío | 202: la campaña queda programada |
scheduled | 202: sale ya | 400: solo un borrador se puede programar |
paused | 409: reanúdala en la app | 409 |
sending | 409: ya hay un envío en curso | 409 |
sent | 409: ya salió | 409 |
failed | 409: no está en un estado enviable | 409 |
| sin enlace de baja en el contenido | 400: no se emite nada | 400: no se programa nada |
| desconocida, o de otra organización | 404 | 404 |
El contenido se renderiza antes de emitir nada. Es exactamente la misma comprobación que
POST /api/v1/render: una campaña cuyo HTML no lleva {{unsubscribeUrl}} responde 400 y no se crea ningún envío. Sin él, cada envío fallaría más tarde por un motivo que la API nunca te muestra. Corrige la plantilla, vuelve a crear la campaña y envía.Llamar dos veces a send nunca envía dos veces. Una campaña que ya salió responde
409 y no se inicia un segundo envío: reintentar tras un timeout es seguro. Si quieres enviar el mismo contenido otra vez, crea una campaña nueva.GET /api/v1/campaigns/{campaignId}: status pasa de draft → sending → sent, y progress se va llenando por el camino. No hay ningún webhook que esperar.
Cuando el sondeo nunca converge
Un202 significa que la solicitud se aceptó, no que el envío vaya a ocurrir. Dos condiciones lo detienen a posteriori, y ninguna cambia la campaña: sigue en draft, progress se queda en cero y nunca se te informa de ningún error.
| Lo que ves | Lo que significa | Qué hacer |
|---|---|---|
draft, progress.total = 0, sendingEnabled = false | El envío masivo está desactivado en toda la cuenta (interruptor de emergencia) | Nada por tu parte: el envío tiene que volver a activarse |
draft, progress.total = 0, sendingEnabled = true | El recipientCount de esta campaña superaría la cuota de envío de 24 h | Espera a que avance la ventana de la cuota y vuelve a llamar a /send |
Deja de sondear si la campaña sigue en
draft sin progreso. Nunca pasará a sent por sí sola. Lee sendingEnabled: false indica el interruptor de emergencia, true indica la cuota diaria; en ambos casos la campaña está intacta y volver a llamar a /send más tarde es seguro, sigue siendo un draft.Una campaña desde cero, de principio a fin
1
Escribe la plantilla
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')unsupportedTags en la respuesta: todo lo que aparezca ahí sigue en bruto en tu HTML.2
Renderízala y lee lo que obtienes
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}'400 aquí significa que el HTML no tiene enlace de baja. Corrige la plantilla con PATCH y vuelve a renderizar: este bucle no cuesta nada.3
Elige el objetivo
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}'GET /api/v1/lists: consulta Descubrimiento. Las etiquetas vienen de la API de audiencia; guarda el id de la que quieras, si la hay.4
Crea la campaña
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
Cuenta los destinatarios antes de comprometerte
bash
curl -s -H "x-api-key: $AGENTMAIL_API_KEY" \
"https://www.agentsmail.io/api/v1/campaigns/$CAMPAIGN_ID" | jq '.data.recipientCount'6
Envía
bash
curl -s -X POST -H "x-api-key: $AGENTMAIL_API_KEY" \
"https://www.agentsmail.io/api/v1/campaigns/$CAMPAIGN_ID/send"202 significa que el envío se aceptó, no que haya terminado.7
Síguelo
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}'status sea sent. No vuelvas a llamar a /send mientras esperas: responde 409. Si la campaña sigue en draft con progress.total a cero, deja de sondear y lee Cuando el sondeo nunca converge más arriba: el envío está desactivado o la cuota diaria está llena.