Skip to content

Commit dcccfa2

Browse files
baditaflorinclaude
andauthored
fix(mail-gateway): wire Brevo verified sender and DNS role 409 idempotency (#18)
- Add BREVO_FROM_EMAIL / BREVO_FROM_NAME env vars so the gateway uses a verified Brevo sender address instead of the per-profile address (which is not registered in Brevo). Profile sender_email becomes Reply-To so replies still land in the correct mailbox. - Default mail_platform_brevo_sender_email to server@lv3.org (verified). - Inject BREVO_FROM_EMAIL and BREVO_FROM_NAME in both env templates (mail-gateway.env.j2 and mail-gateway.env.ctmpl.j2). - hetzner_dns_records: treat HTTP 409 Conflict as "record already exists" (no change needed) rather than a hard failure. Prevents converge aborts when DNS records are already present in the zone. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent d46803e commit dcccfa2

5 files changed

Lines changed: 25 additions & 8 deletions

File tree

collections/ansible_collections/lv3/platform/roles/hetzner_dns_records/tasks/record.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@
128128
status_code:
129129
- 200
130130
- 201
131+
- 409
131132
- 429
132133
- 500
133134
- 502
@@ -140,9 +141,10 @@
140141
- dns_provider_boundary_matching_records | length == 0
141142
retries: 5
142143
delay: 2
143-
until: hetzner_dns_record_create_response.status in [200, 201]
144-
failed_when: hetzner_dns_record_create_response.status not in [200, 201]
145-
changed_when: true
144+
until: hetzner_dns_record_create_response.status in [200, 201, 409]
145+
failed_when: hetzner_dns_record_create_response.status not in [200, 201, 409]
146+
# 409 = record already exists at provider; treat as no change needed
147+
changed_when: hetzner_dns_record_create_response.status in [200, 201]
146148
no_log: true
147149

148150
# Update path on the new Hetzner Console API:

collections/ansible_collections/lv3/platform/roles/mail_platform_runtime/defaults/main.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ mail_platform_metrics_password_local_file: "{{ repo_shared_local_root }}/mail-pl
5959
mail_platform_gateway_api_key_local_file: "{{ repo_shared_local_root }}/mail-platform/gateway-api-key.txt"
6060
mail_platform_brevo_api_key_local_file: "{{ repo_shared_local_root }}/mail-platform/brevo-api-key.txt"
6161
mail_platform_brevo_api_url: https://api.brevo.com/v3/smtp/email
62-
mail_platform_brevo_sender_email: operator@example.com
63-
mail_platform_brevo_sender_name: Florin Mail Bridge
62+
mail_platform_brevo_sender_email: server@lv3.org
63+
mail_platform_brevo_sender_name: LV3 Platform
6464
mail_platform_reply_to_email: "{{ mail_platform_mailbox_address }}"
6565
mail_platform_gateway_bind_port: "{{ mail_platform_gateway_port }}"
6666
mail_platform_gateway_force_brevo_fallback: false

collections/ansible_collections/lv3/platform/roles/mail_platform_runtime/files/mail-gateway/app.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ def env_bool(name: str, default: bool) -> bool:
2626
PROFILES_FILE = Path(os.getenv("NOTIFICATION_PROFILES_FILE", "/config/notification-profiles.json"))
2727
BREVO_API_KEY = os.environ["BREVO_API_KEY"]
2828
BREVO_API_URL = os.getenv("BREVO_API_URL", "https://api.brevo.com/v3/smtp/email")
29+
# BREVO_FROM_EMAIL overrides the per-profile sender for Brevo sends.
30+
# Set this to a verified Brevo sender address; falls back to DEFAULT_FROM_EMAIL.
31+
BREVO_FROM_EMAIL = os.getenv("BREVO_FROM_EMAIL")
32+
BREVO_FROM_NAME = os.getenv("BREVO_FROM_NAME")
2933
DEFAULT_FROM_EMAIL = os.environ["DEFAULT_FROM_EMAIL"]
3034
DEFAULT_FROM_NAME = os.getenv("DEFAULT_FROM_NAME", "LV3 Mail Gateway")
3135
DEFAULT_REPLY_TO_EMAIL = os.getenv("DEFAULT_REPLY_TO_EMAIL")
@@ -387,10 +391,15 @@ def send_via_local_smtp(payload: SendRequest, profile: dict[str, Any]) -> None:
387391

388392

389393
async def send_via_brevo(payload: SendRequest, profile: dict[str, Any]) -> dict[str, Any]:
394+
# When BREVO_FROM_EMAIL is set, use it as the Brevo sender (it must be a
395+
# verified Brevo sender address). The profile's sender_email becomes the
396+
# Reply-To so replies still land in the right mailbox.
397+
brevo_from_email = BREVO_FROM_EMAIL or profile["sender_email"] or DEFAULT_FROM_EMAIL
398+
brevo_from_name = BREVO_FROM_NAME or profile["sender_name"] or DEFAULT_FROM_NAME
390399
body: dict[str, Any] = {
391400
"sender": {
392-
"name": profile["sender_name"] or DEFAULT_FROM_NAME,
393-
"email": profile["sender_email"] or DEFAULT_FROM_EMAIL,
401+
"name": brevo_from_name,
402+
"email": brevo_from_email,
394403
},
395404
"to": [{"email": address} for address in payload.to],
396405
"subject": payload.subject,
@@ -399,7 +408,9 @@ async def send_via_brevo(payload: SendRequest, profile: dict[str, Any]) -> dict[
399408
body["textContent"] = payload.text
400409
if payload.html:
401410
body["htmlContent"] = payload.html
402-
reply_to = profile["reply_to"] or DEFAULT_REPLY_TO_EMAIL
411+
# Use profile reply_to, or fall back to profile sender_email so replies
412+
# reach the intended mailbox even when the From is the Brevo relay address.
413+
reply_to = profile["reply_to"] or profile["sender_email"] or DEFAULT_REPLY_TO_EMAIL
403414
if reply_to:
404415
body["replyTo"] = {"email": reply_to}
405416

collections/ansible_collections/lv3/platform/roles/mail_platform_runtime/templates/mail-gateway.env.ctmpl.j2

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
BREVO_API_KEY=[[ with secret "kv/data/{{ mail_platform_openbao_secret_path }}" ]][[ .Data.data.brevo_api_key ]][[ end ]]
22
BREVO_API_URL={{ mail_platform_brevo_api_url }}
3+
BREVO_FROM_EMAIL={{ mail_platform_brevo_sender_email }}
4+
BREVO_FROM_NAME={{ mail_platform_brevo_sender_name }}
35
DEFAULT_FROM_EMAIL={{ mail_platform_brevo_sender_email }}
46
DEFAULT_FROM_NAME={{ mail_platform_brevo_sender_name }}
57
DEFAULT_REPLY_TO_EMAIL={{ mail_platform_reply_to_email }}

collections/ansible_collections/lv3/platform/roles/mail_platform_runtime/templates/mail-gateway.env.j2

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
BREVO_API_KEY={{ lookup('file', mail_platform_brevo_api_key_local_file) | trim }}
22
BREVO_API_URL={{ mail_platform_brevo_api_url }}
3+
BREVO_FROM_EMAIL={{ mail_platform_brevo_sender_email }}
4+
BREVO_FROM_NAME={{ mail_platform_brevo_sender_name }}
35
DEFAULT_FROM_EMAIL={{ mail_platform_brevo_sender_email }}
46
DEFAULT_FROM_NAME={{ mail_platform_brevo_sender_name }}
57
DEFAULT_REPLY_TO_EMAIL={{ mail_platform_reply_to_email }}

0 commit comments

Comments
 (0)