Campañas

Una campaña es un correo, una audiencia, una salida. Todo lo que ocurre antes del envío es reversible; el envío en sí nunca lo es.

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

CampoDóndeObligatorioDescripción
scheduledAtcuerponoInstante ISO 8601 con un desfase explícito. Si se omite, la campaña sale ya
Sin cuerpo, con un cuerpo vacío o con un cuerpo sin 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ñaPOST /sendcon scheduledAt
draft202: se solicita el envío202: la campaña queda programada
scheduled202: sale ya400: solo un borrador se puede programar
paused409: reanúdala en la app409
sending409: ya hay un envío en curso409
sent409: ya salió409
failed409: no está en un estado enviable409
sin enlace de baja en el contenido400: no se emite nada400: no se programa nada
desconocida, o de otra organización404404
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.
Después consulta periódicamente 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

Un 202 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 vesLo que significaQué hacer
draft, progress.total = 0, sendingEnabled = falseEl 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 = trueEl recipientCount de esta campaña superaría la cuota de envío de 24 hEspera 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')
Revisa 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}'
Un 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}'
Los ids de lista vienen de 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'
Cero significa que el objetivo no coincide con nadie. Enviar no haría nada: corrige primero el objetivo.
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}'
Sondea hasta que 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.