Ir al contenido

Referencia técnica de correo

La guía de envío y resultados explica todos los métodos de Mail, IMailManager, PendingMail y Message. La guía de clases de correo desarrolla Mailable, Envelope y Address. Aquí se reúnen los componentes de composición y las utilidades de nivel inferior.

ComponenteDocumentación
MailManager e IMailManagerFachada, DI y ciclo de vida; operaciones fluidas y terminales.
PendingMail, Message, MailOptionsCadenas independientes, callbacks y precedencia.
Mailable, Envelope, AddressClases, hooks, destinatarios y normalización.
ContentTexto, HTML, vistas y contexto.
AttachmentTodos sus campos y fromStorage().
MailResult, MailStatus, PreparedMailDatos de entrega y resultados.
SmtpSettings, MailEncryption y configuraciónReferencia de configuración.
IMailTransport, FileTransport, SmtpTransport y factoríasTransportes nativos y extensiones.

El paquete orionis.mail exporta Address, Attachment, Content, Envelope, MailResult, MailStatus, Mailable, Message y PendingMail. Los servicios, contratos y componentes de bajo nivel se importan desde los módulos indicados en sus ejemplos. La fachada se importa desde orionis.support.facades.

MailComposer(view: IViewEngine, storage: IStorageManager) -> None
async prepare(
envelope: Envelope,
content: Content,
attachments: tuple[Attachment, ...],
) -> PreparedMail

El constructor conserva los servicios de vistas y Storage; no el estado de un mensaje. El provider lo registra como singleton. Cada prepare() mantiene sus datos locales, comprueba remitente y destinatarios, renderiza primero texto y después HTML, carga los adjuntos en orden y compone MIME completo mediante Loop.execute() fuera del event loop.

prepare() no selecciona un mailer, no aplica el remitente global y no envía por SMTP ni publica un .eml. Esas responsabilidades pertenecen al manager y al transporte. Al utilizar directamente este servicio debes aportar un Envelope con remitente y destinatarios ya resueltos.

from orionis.mail import Content, Envelope
from orionis.mail.composer import MailComposer
class InspectMail:
def __init__(self, composer: MailComposer) -> None:
self.composer = composer
async def prepare(self) -> dict:
prepared = await self.composer.prepare(
Envelope(
subject="Preview",
from_address="notifications@example.com",
to="ana@example.com",
),
Content(text="Hello, Ana.", html="<p>Hello, Ana.</p>"),
(),
)
return {
"message_id": prepared.message_id,
"mime_bytes": len(prepared.mime),
"smtp_utf8": prepared.smtp_utf8,
}

Inyecta InspectMail en un componente de la aplicación. Este ejemplo solo compone: no utiliza un transporte. PreparedMail.mime contiene bytes MIME; no es una respuesta HTTP ni un stream abierto.

  • Genera Date y Message-ID por operación. La dirección del remitente aporta el dominio del ID.
  • Conserva el texto y HTML como alternativas MIME cuando existen ambos. Los cuerpos se codifican como quoted-printable.
  • Añade To, Cc y Reply-To visibles; Bcc pertenece únicamente a la unión de destinatarios del sobre de transporte.
  • Detecta direcciones addr-spec no ASCII para marcar smtp_utf8; los nombres visibles Unicode se codifican como encabezados y no equivalen por sí solos a una dirección internacional.
  • Materializa los adjuntos y el MIME completo en memoria. La lectura de Storage y la limpieza de sus streams se describen en adjuntos.
  • Los fallos de renderizado se convierten a MailCompositionException con su causa. Los de lectura de adjuntos se tratan como MailAttachmentException; errores de tipos/valores al codificar MIME se convierten a MailCompositionException.

El mensaje se prepara antes de construir el transporte. Un error de vista o archivo impide llegar a esa entrega; no demuestra que otras operaciones de tu servicio deban revertirse.

Estos componentes se utilizan al implementar integraciones. El manager ya los selecciona para el envío habitual:

FileTransport(path: Path) -> None
create_file_transport(app: IApplication, config: Mapping[str, object]) -> IMailTransport
SmtpTransport(settings: SmtpSettings) -> None
create_smtp_transport(app: IApplication, config: Mapping[str, object]) -> IMailTransport

Los componentes file se importan de orionis.mail.transports.file y los SMTP de orionis.mail.transports.smtp. El constructor file conserva la ruta; su factoría valida config["path"] (fallback storage/mail) y ancla rutas relativas a app.basePath. El constructor SMTP conserva las opciones normalizadas; su factoría ejecuta SmtpSettings.fromConfig(config). Ninguno de esos constructores conecta o publica un mensaje.

Ambos transportes cumplen la firma async send(message: PreparedMail, *, mailer: str, driver: str) -> MailResult. El argumento message es obligatorio, al igual que los nombres keyword-only mailer y driver. File devuelve almacenamiento confirmado; SMTP devuelve aceptación completa o parcial. Las diferencias de errores, cierre, cancelación y publicación se explican en transportes.

En orionis.mail.types:

Recipients = str | Address | list[str | Address] | tuple[str | Address, ...]
MessageCallback = Callable[[Message], Message | Awaitable[Message | None] | None]
Delivery = Callable[
[str | None, Envelope, Content, tuple[Attachment, ...]],
Awaitable[MailResult],
]
TransportFactory = Callable[
[IApplication, Mapping[str, object]],
IMailTransport | Awaitable[IMailTransport],
]

Recipients define las formas aceptadas para buzones. MessageCallback recibe el configurador de una operación directa y retorna None o ese mismo objeto, directamente o mediante awaitable. Delivery describe el callback de entrega que recibe PendingMail: nombre opcional de mailer, sobre, contenido y adjuntos. TransportFactory es el contrato de factoría de extend(); debe devolver una instancia que cumpla IMailTransport por herencia.

orionis.mail.composer.LoadedAttachment declara tuple[Attachment, bytes, str | None]: declaración, bytes completos y MIME opcional obtenido durante la lectura. El composer lo utiliza para preparar el MIME; los consumidores habituales declaran Attachment y dejan esa resolución al servicio.

Estas funciones públicas están en orionis.mail.functions. Son síncronas. Los componentes de alto nivel ya las utilizan; no necesitas llamarlas antes de cada envío ni aportan métodos adicionales al builder.

header_value(value: str, label: str) → str

Sección titulada «header_value(value: str, label: str) → str»

Retorna el texto original si no contiene caracteres de control, formato, surrogates o separadores Unicode de línea/párrafo. Una cadena vacía es válida, por ejemplo para el asunto. Un tipo incorrecto o carácter rechazado produce MailCompositionException; label identifica el campo en el diagnóstico.

from orionis.mail.functions import header_value
assert header_value("Monthly report", "Subject") == "Monthly report"
assert header_value("", "Subject") == ""

Valida el nombre visible del adjunto y lo devuelve sin modificar. Rechaza controles, valores vacíos o solo espacios, ./.. y /, \ o :. No acepta una ruta completa. Los errores se expresan como MailAttachmentException.

from orionis.mail.functions import attachment_name
assert attachment_name("Annual report.pdf") == "Annual report.pdf"

Devuelve un par type/subtype en minúsculas. Exige dos tokens MIME no vacíos; no admite parámetros como ; charset=utf-8, espacios o una cadena sin /. Un valor inválido genera MailAttachmentException. Valida la etiqueta, no inspecciona los bytes de un archivo.

from orionis.mail.functions import media_type
assert media_type("Application/PDF") == "application/pdf"

freeze_owned(value: object, active: frozenset[int] = frozenset()) → object

Sección titulada «freeze_owned(value: object, active: frozenset[int] = frozenset()) → object»

Crea snapshots recursivos de contenedores propios: mappings pasan a MappingProxyType, listas/tuplas a tuplas y sets/frozensets a frozensets. Los objetos opacos se conservan por referencia. Los contenedores cíclicos generan MailCompositionException; active lleva los ancestros durante la recursión y normalmente se deja con su valor predeterminado. No equivale a copiar profundamente cualquier servicio u objeto de negocio.

from orionis.mail.functions import freeze_owned
source = {"tags": ["receipt"]}
snapshot = freeze_owned(source)
source["tags"].append("updated")
assert snapshot["tags"] == ("receipt",)

sanitize_reason(value: str | bytes, sensitive: tuple[str, …] = ()) → str

Sección titulada «sanitize_reason(value: str | bytes, sensitive: tuple[str, …] = ()) → str»

Decodifica bytes UTF-8 con reemplazo, sustituye los secretos no vacíos declarados —primero los más largos—, convierte los controles rechazados en espacios y limita la salida a 512 caracteres. Solo redacta los secretos que recibe. MailResult limpia los controles y acota razones; el transporte debe redactar sus credenciales antes de construirlo.

from orionis.mail.functions import sanitize_reason
reason = sanitize_reason(b"Rejected\r\ncredential=example-secret", ("example-secret",))
assert "example-secret" not in reason
assert "\r" not in reason and "\n" not in reason

Los helpers de direcciones one_address() y addresses() están documentados con sus firmas y ejemplos en Address.

register() -> None registra MailComposer e IMailManager → MailManager como singletons. async boot() -> None ejecuta await Mail.pin(). Es un provider nativo ordinario, no diferido. El registro de la aplicación estándar ya lo incluye; no agregues una segunda instancia por petición.

Mail.getFacadeAccessor() → type[IMailManager]

Sección titulada «Mail.getFacadeAccessor() → type[IMailManager]»

Este método de clase retorna el contrato con el que la fachada resuelve el manager. No lee configuración ni envía un mensaje. Los métodos heredados de la fachada permiten resolver el servicio explícitamente; el uso de Mail y DI muestra los contextos adecuados.

ClaseFinalidad
MailExceptionBase común del subsistema.
MailConfigurationExceptionMailer/driver, factoría o ajustes inválidos.
MailCompositionExceptionDeclaraciones, callbacks, direcciones, vistas o MIME inválidos.
MailAttachmentExceptionSubclase de composición para metadatos o lectura de adjuntos.
MailTransportExceptionFallo de entrega/publicación; no garantiza que el servidor no haya recibido datos.

Estas clases no incorporan un método de retry ni un atributo público de rollback. El contexto y la causa original pueden acompañar la excepción. Un resultado SMTP parcial tiene datos propios en MailResult; no se representa simplemente como «ningún envío». Consulta resultados y errores.