Skip to content

Cartero

Field Value
Owner Alquemis (DRI) — original author Olger Ávila (IT)
Status live
Runs on Office Windows host (C:\Trabajo\cartero); Azure-capable (managed identity → Key Vault)
Connectivity Internal only — unauthenticated POST /send, must stay on a trusted network. Outbound to Exchange Online (SMTP 587). Not exposed via Cloudflare Tunnel.
Hostname internal only, no public endpoint
Repository Alquemis-Automation/Cartero (private)

What it is

Cartero is Alquemis' internal transactional email service: it sends mail as cartero@alquemis.com through Microsoft 365 / Exchange Online using SMTP AUTH + OAuth 2.0 (client-credentials, app-only) — deliberately avoiding Basic Auth, which Microsoft is retiring (deprecation through 2026, retirement H2 2027).

It ships in two forms:

  • MailSender — a reusable Python class (import and call .send(...)); caches the MSAL token across sends, so create one instance and reuse it.
  • mail_service.py — a Starlette / Uvicorn REST microservice wrapping MailSender, so other automations can send mail over HTTP (same mold as the internal pdf_service).

The From address is always cartero@alquemis.com (fixed policy — callers can't override the sender); a caller-supplied from is used only as the Reply-To, which otherwise defaults to soporte@bio-land.com. Built with msal + azure-identity, Python 3.

HTTP API

The microservice listens on port 8182 (configurable) and exposes:

Method + path Purpose
POST /send Send one email (JSON body below). Returns {ok: true, from, reply_to} or {ok: false, error}.
GET /health Liveness — returns service name, version, sender, default reply-to, timestamp.

POST /send JSON body:

Field Type Notes
to str | list required — recipient(s); ; and , separators accepted (PKCORREO format)
subject str required
body str plain-text body (optional)
html str HTML body (optional)
cc / bcc str | list optional
from str ignored for the From; used as Reply-To if reply_to is absent
reply_to str optional; default soporte@bio-land.com
attachments list each item {filename, content_base64, mime} or {filename, path, mime} (path/UNC reachable by the service)

Error codes: 400 bad request (missing to/subject), 502 send failure (MailSendError), 500 unexpected.

How it's reached

Internal callers hit http://<host>:8182/send on the office LAN. There is no authentication on the endpoint and it is not published through the Cloudflare Tunnel — it must stay on a trusted network (see security notes). Outbound, the service connects to smtp.office365.com:587 (STARTTLS) to submit mail.

Configuration

All settings have defaults in config.py and are overridable via environment variables (see .env.example; .env is never committed). Key ones:

Variable Default Purpose
CARTERO_CLIENT_SECRET Entra app secret. If unset, read from Key Vault.
CARTERO_TENANT_ID / CARTERO_CLIENT_ID Bioland1 tenant / app (in config.py) Entra app identity
CARTERO_SENDER cartero@alquemis.com Fixed From (UPN)
CARTERO_REPLY_TO soporte@bio-land.com Default Reply-To
CARTERO_KEYVAULT_URL / _SECRET https://generales.vault.azure.net/ / cartero-smtp-client-secret Key Vault fallback for the secret
CARTERO_SMTP_HOST / _PORT / _TIMEOUT smtp.office365.com / 587 / 30 SMTP endpoint
CARTERO_MAX_RETRIES / BACKOFF_BASE / _CAP 3 / 2.0 / 30.0 Transient-error retry / backoff (seconds)
MAIL_SERVICE_HOST / _PORT / MAIL_LOG_LEVEL 0.0.0.0 / 8182 / INFO Microservice bind + logging

Secret resolution (config.obtener_client_secret()): CARTERO_CLIENT_SECRET from the environment first, else Azure Key Vault (Generales vault) via DefaultAzureCredential — a managed identity on Azure (role Key Vault Secrets User), or a service-principal / signed-in dev identity on-prem. The tenant/client IDs in config.py are non-secret identifiers; the secret itself is never in code.

Security notes

  • Unauthenticated endpoint. POST /send has no auth or rate limiting — anyone who can reach the port can send mail as cartero@alquemis.com. Keep it on a trusted network only; add auth before any wider exposure.
  • Fixed sender. From is locked to cartero@alquemis.com; callers cannot spoof another From.
  • No secrets in code/repo. The app secret comes from env or Key Vault; only non-secret tenant/client IDs live in config.py.
  • Retries fire only on transient SMTP errors (timeouts, disconnects, 4.x) with exponential backoff; permanent errors (5.x, auth 535) fail fast with MailSendError.

Dependencies

  • Microsoft 365 / Exchange Onlinesmtp.office365.com:587; requires SMTP.SendAsApp admin consent, a service principal with FullAccess + SendAs on the mailbox, and SMTP AUTH enabled on the mailbox. Already configured in the Bioland1 tenant.
  • Entra ID app registration (client-credentials, single tenant).
  • Azure Key Vault (Generales, secret cartero-smtp-client-secret) — fallback secret source.
  • Python: msal, azure-identity, azure-keyvault-secrets, python-dotenv, starlette, uvicorn.
  • Consumers: internal Alquemis automations that need to send mail over HTTP.

Known limitations

  • Not a bulk / marketing mailer. Exchange throttles SMTP client submission; at volume, move to Microsoft Graph sendMail or High Volume Email.
  • Outbound only. Sends mail; does not read or receive.
  • Secret expiry. The Entra app secret expires — delivery breaks if it isn't rotated (see the runbook). Migrating to certificate auth is under consideration.
  • Single sender. Only cartero@alquemis.com.