Ir al contenido

Entrega por correo

Cuando el documento queda accepted (o se declara la contingencia de la DIAN, que se entrega sin validación previa), foliios arma el paquete y lo envía por correo a los correos del comprador que están en el documento.

Un correo con un ZIP que contiene:

  • el contenedor firmado (AttachedDocument): el XML del documento y la respuesta de la DIAN, y
  • la representación gráfica en PDF.

La entrega no depende de la validación de la DIAN para la contingencia: en ese caso se envía el tipo 04 y, cuando la DIAN lo valida, se entrega otra vez el contenedor validado. En el sandbox no sale ningún correo: va al buzón de pruebas del portal (HTML, XML, PDF y ZIP), y el PDF lleva la marca de agua «SANDBOX · SIN VALIDEZ FISCAL». En habilitación (sk_test_), por defecto, tampoco sale ningún correo: cada uno se guarda en el buzón de habilitación de la empresa, la entrega queda sent y el intento dice destination: inbox (el correo al comprador en habilitación). La excepción es el tope diario de correos reales que foliios habilita a pedido para una empresa: esos correos sí se envían, con [PRUEBA] en el asunto y destination: email. En producción (sk_live_) el correo llega siempre al comprador, con destination: email.

delivery en el documento es un estado aparte del de la DIAN (null mientras no empieza):

"delivery": {
"status": "sent",
"trigger": "accepted",
"recipients": ["facturas@andina.example"],
"failure_code": null,
"reason": null,
"next_attempt_at": null,
"attempts": [{"trigger": "accepted", "number": 1, "outcome": "sent"}]
}
delivery.status Significa
pending Espera su envío o el siguiente reintento (next_attempt_at).
sent El servicio de correo aceptó el mensaje. No garantiza que el comprador lo haya leído.
failed No se enviará sin intervención; failure_code dice por qué.
not_applicable El documento no tiene correo del comprador (por ejemplo, consumidor final). Se entrega por otro medio: el PDF impreso. La emisión no falla por esto.

Principales failure_code: mailer_rejected, retries_exhausted (seis envíos con fallo temporal), outcome_unknown (el envío se interrumpió y no se reenvía solo), recipient_bounced, recipient_suppressed, invalid_recipient_address, artifacts_unavailable, certificate_unavailable, issuer_not_configured, rendering_failed y package_too_large. Los fallos temporales se reintentan solos a 1 min, 5 min, 15 min, 1 h y 3 h.

El reason es un mensaje fijo en español por clase de fallo, sin detalles internos.

Ventana de terminal
curl -s -X POST "https://api.foliios.com/v1/companies/$FOLIIOS_COMPANY_ID/documents/$DOCUMENT_ID/deliver" \
-H "Authorization: Bearer $FOLIIOS_API_KEY" -H "Content-Type: application/json" \
-d '{"email": "contabilidad@cliente.example"}'

El cuerpo es opcional: sin él, va a los correos del comprador. Requiere documents:write. Responde 202 con delivery.status en pending; el envío es asíncrono.

Respuesta Por qué
409 document_not_deliverable El documento no está accepted ni emitido en contingencia.
422 delivery_recipient_required No hay correo del comprador y no enviaste email.
409 recipient_suppressed La dirección (o todas las del comprador) está suprimida. No se encola nada.
409 concurrent_modification Se está enviando en ese momento.

No es idempotente: cada llamada es un correo más.

from foliios_inicio import EMPRESA, llamar
estado, documento, _ = llamar(
"POST",
f"/v1/companies/{EMPRESA}/documents/{documento_id}/deliver",
{"email": "contabilidad@cliente.example"},
)
print(estado, documento["delivery"]["status"]) # 202 pending
Ventana de terminal
# PDF (inline)
curl -s -H "Authorization: Bearer $FOLIIOS_API_KEY" -o factura.pdf \
"https://api.foliios.com/v1/companies/$FOLIIOS_COMPANY_ID/documents/$DOCUMENT_ID/pdf"
# El ZIP entregado; con ?format=xml, el contenedor firmado solo
curl -s -H "Authorization: Bearer $FOLIIOS_API_KEY" -o entrega.zip \
"https://api.foliios.com/v1/companies/$FOLIIOS_COMPANY_ID/documents/$DOCUMENT_ID/attached-document"

Los dos se generan con la entrega: antes responden 409 pdf_not_available o 409 attached_document_not_available. Un documento sin correo del comprador también los tiene: sirve para imprimir.

Un rebote permanente falla la entrega con recipient_bounced y suprime la dirección para esa empresa. Una queja (el destinatario marcó el correo como spam) registra la dirección y emite el evento document.delivery_complained; la entrega no cambia de estado. Con varios destinatarios, la entrega solo falla si todos tienen un fallo definitivo; si solo algunos, sigue sent y el intento lleva una nota en attempts[].feedback.

GET /v1/companies/{company_id}/suppressions (permiso documents:read) lista las direcciones suprimidas de la empresa, con email, reason (bounce_permanent o complaint), suppressed_at y document_id. Es un registro por empresa: no reemplaza la lista de supresión del servicio de correo, así que una dirección puede seguir bloqueada aunque tu lista esté vacía. Quitar una dirección bloqueada lo hace soporte, no la API.

  • No hay reintento tras un rebote temporal. Los reintentos automáticos son para cuando el servicio de correo no acepta el mensaje. Si lo aceptó y después el buzón del comprador lo rebota de forma temporal (buzón lleno, servidor caído), la entrega sigue sent y el intento lleva la nota en attempts[].feedback (bounce_transient); foliios no programa otro envío, porque el servicio de correo ya reintentó antes de reportarlo. Un rebote temporal no suprime la dirección ni cuenta para el aviso de rebotes. Si el comprador no recibió el documento, reenvíalo.
  • No hay seguimiento de apertura. sent dice que el servicio de correo aceptó el mensaje, no que el comprador lo abrió. El correo no lleva píxeles ni enlaces de seguimiento, y no hay evento de apertura ni de clic.
  • El historial guarda 50 intentos. attempts trae los últimos 50 envíos del documento; los más viejos se descartan.
  • Un fallo del contador de reenvíos deja pasar el reenvío. En producción y en habilitación los reenvíos tienen tope: 10 por minuto por empresa y 5 por documento en 24 horas; al superarlo, 429 rate_limited con Retry-After. Si el contador por minuto falla de nuestro lado, el reenvío se acepta en vez de rechazarse. El tope por documento no depende de ese contador.

Cada correo debe tener una forma válida (parte local sin comas, punto y coma, paréntesis ni comillas; dominio de dos o más partes, sin direcciones IP). Un dominio con tildes o ñ va en punycode (ventas@xn--espaa-rta.co). Si no, la API responde 422 invalid_email. Para el comprador facturador electrónico, el correo debe ser el que registró en la DIAN o el medio que él señaló.

document.delivered, document.delivery_failed y document.delivery_not_applicable, con data.delivery. document.delivery_complained trae data.complaint.complained_at (cuándo se reportó la queja) y no trae data.delivery ni la dirección: consúltala en la lista de direcciones suprimidas. Llega una vez por queja y documento. Su forma está en eventos de webhook. Para probarlos sin esperar, dispara un evento de prueba (ver webhooks) o provoca el fallo con los escenarios delivery_rejected y delivery_transient (escenarios).