Enviar o programar

POST /api/v1/campaigns/{campaignId}/send Lanza el envío y responde 202 de inmediato. El envío en sí se ejecuta en segundo plano, al ritmo que permite la cuota de envío: la solicitud nunca lo espera. Llamado sin cuerpo, envía ahora. Llamado con un scheduledAt, reserva el envío para ese instante: mismo endpoint, misma clave, mismos permisos.
json
{"success": true, "data": {"campaignId": "…", "status": "send_requested"}}

Programación: un campo opcional, el mismo endpoint

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.

Autorización

Rol en la organizaciónEste endpoint
OWNERPermitido
ADMINPermitido
MEMBERRechazado — 403
Una clave lleva el rol que su creador tiene en esta organización, nunca un rol en AgentsMail en sí. Consulta Claves de API.

Cuerpo de la solicitud

CampoTipoObligatorioDescripción
scheduledAtstringnoInstante ISO 8601 con un desfase explícito. Si se omite, la campaña sale ya

Ejemplo

bash
curl -X POST -H "x-api-key: $AGENTMAIL_API_KEY" \
  "https://www.agentsmail.io/api/v1/campaigns/$CAMPAIGN_ID/send"

Respuesta

  • 202 Accepted: el envío está en cola; síguelo en la campaña.
  • 401 Unauthorized: clave ausente o no válida, o su propietario dejó la organización.
  • 403 Forbidden: el propietario de la clave es member de la organización, no admin.
  • 404 Not Found: no existe ese recurso, o pertenece a otra organización. La API nunca confirma que un id existe a quien no tiene derecho sobre él.
  • 429 Too Many Requests: más de 120 solicitudes en un minuto para esta clave.

Campos de la respuesta

CampoTipoDescripción
successbooleanIndica si la operación tuvo éxito
data.statusstringsend_requested cuando sale ya, scheduled cuando se indicó una fecha

Ejemplo de respuesta

json
{
  "success": true,
  "data": {
    "status": "subscribed"
  }
}

Receta para agentes

Un envío es asíncrono: la llamada vuelve en cuanto se acepta la salida, no cuando los correos han salido. Sondea la campaña para seguirlo, y deja de sondear cuando el envío está desactivado, o el bucle nunca converge.
js
await fetch(`${base}/campaigns/${campaignId}/send`, {method: 'POST', headers})

for (;;) {
  const {data} = await (
    await fetch(`${base}/campaigns/${campaignId}`, {headers})
  ).json()

  if (!data.sendingEnabled) throw new Error('sending is paused: polling would never end')
  if (data.status === 'sent' || data.status === 'failed') return data
  await new Promise((resolve) => setTimeout(resolve, 5000))
}
Nunca llames a esto sin la aprobación humana explícita para esa campaña concreta. La aprobación de un envío nunca cubre el siguiente.

Relacionado