SMTP Endpoints¶
Everything the Processor needs to reach a mail server is a profile.
SELECT pgrelay_notifier.create_profile(
p_profile_name => 'mailer',
p_transport => 'smtp',
p_profile => '{
"host": "smtp.example.com",
"port": 587,
"security": "starttls",
"username": "app-mailer",
"password": "_env:MAILER_SMTP_PASSWORD",
"from": "[email protected]"
}'::jsonb,
p_channel => 'notifications'
);
Profile keys¶
Unknown keys are ignored, so profiles are forward-compatible with future Processor releases.
| Key | Required | Meaning |
|---|---|---|
host |
✔ | SMTP server |
port |
Default 587 | |
security |
starttls (default), tls (implicit TLS, e.g. port 465), none (internal relays only) |
|
auth |
plain, login, none. Default: plain if username present, else none |
|
username |
with auth | SMTP username |
password |
with auth | The SMTP password — must be an "_env:VAR_NAME" reference (below), never a literal value |
from |
✔ | Envelope sender and From header |
timeout_seconds |
Per-send timeout; default 30, hard cap 120 |
The password never touches the database¶
"_env:VAR_NAME" is a reference, not a value — it names an environment variable on the machine running the pg_relay Processor, resolved fresh at send time. create_profile/update_profile reject a literal string in password outright; there is no way to make a profile store one. Rotating the password is then an environment change plus nothing — no restart, no SQL.
On a systemd host, put the actual value in the Processor's own environment file:
then sudo systemctl restart pg_relay once — only needed when the variable name itself is new to the process; the value is re-read from the running process's environment at send time otherwise.
Common variants¶
-- Implicit TLS (port 465)
'{"host":"smtp.example.com","port":465,"security":"tls","username":"app","from":"[email protected]"}'
-- Internal relay, no TLS, no auth (trusted network only)
'{"host":"relay.internal","port":25,"security":"none","from":"[email protected]"}'
Validating before you commit to a profile¶
create_profile() validates the whole block and lists every problem at once, rather than one error per fix-and-retry cycle. To check a block before creating anything:
Optional: a default sender and Reply-To for this profile¶
p_send_from and p_reply_to on create_profile()/update_profile() set what a notification uses when the sender composes without p_sender/p_reply_to — useful when every message on a profile should come from (or reply to) the same address without every caller having to say so:
SELECT pgrelay_notifier.create_profile('mailer', 'smtp', '{...}'::jsonb,
p_channel := 'notifications',
p_send_from := '[email protected]', p_reply_to := '[email protected]');
Resolution happens once, at compose time — an explicit sender argument beats the profile default, which beats the profile JSON's own from key — and is recorded on the notification, so a later edit to the profile's defaults never changes a message already composed. Clear either with an empty string. This applies equally to Microsoft 365 profiles, where send_from lands on the Graph sender key — see Microsoft 365 Endpoints.