Ir al contenido

Entrega por correo

Una factura no queda expedida hasta que llega al comprador. 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. Las normas están en el marco normativo, con revisión pendiente de Armando.

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».

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 solo la registra. 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.

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. 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).