SMTP connection¶
Connect a task to an SMTP mail relay over a managed Leoflow Connection. The
host, port, credentials, and an Extra blob (from_email, timeout) are
encrypted at rest and delivered to the task as AIRFLOW_CONN_<CONN_ID>.
URI shape¶
The control plane percent-escapes the password; SmtpHook (which parses
AIRFLOW_CONN_<ID>) un-escapes it back. The Extra blob rides in the
__extra__ query param.
Fields the UI asks for¶
| Field | Required | Notes |
|---|---|---|
| Conn Id | yes | e.g. smtp_default. Exported as AIRFLOW_CONN_SMTP_DEFAULT. |
| Conn Type | yes | smtp. |
| Host | yes | The relay host, e.g. smtp.example.com. |
| Port | optional | Defaults to 587 (STARTTLS) or 465 (implicit TLS). |
| Login | yes | The SMTP username. |
| Password | yes | Stored encrypted at rest (ADR 0019). Percent-escaped in the URI. |
| Extra | optional | JSON, e.g. {"from_email":"bot@example.com","timeout":30}. |
Example DAG¶
The hook is imported inside the task body so DAG parsing stays import-light.
from airflow.decorators import dag, task
from pendulum import datetime
@dag(schedule=None, start_date=datetime(2026, 1, 1), catchup=False)
def smtp_notify():
@task
def send():
from airflow.providers.smtp.hooks.smtp import SmtpHook
with SmtpHook(smtp_conn_id="smtp_default") as hook:
hook.send_email_smtp(
to="ops@example.com",
subject="Leoflow run complete",
html_content="<p>The pipeline finished.</p>",
)
send()
smtp_notify()
Security notes¶
- TLS in transit: prefer port
587(STARTTLS) or465(implicit TLS). - Secrets in logs: never
print()the URI โ it carries the password. Log the host + login only. - gRPC channel (agent โ control plane): secrets are served only over an authenticated channel (ADR 0021); Pro must run with TLS.