feat: send mailings over MailJet smtp in dedicated worker process

This commit is contained in:
Björn Fromme
2026-08-12 15:47:04 +02:00
parent a5c98025d0
commit e0a9562fc6
14 changed files with 480 additions and 44 deletions
+6 -1
View File
@@ -1,3 +1,8 @@
framework:
mailer:
dsn: '%env(MAILER_DSN)%'
# "main" has to stay first: it is the transport every mail without an explicit
# X-Transport header goes out over, which is all of them apart from the teamer
# mailing. Reordering this quietly moves the whole application to Mailjet.
transports:
main: '%env(MAILER_DSN)%'
mailing: '%env(MAILING_MAILER_DSN)%'
+36
View File
@@ -12,6 +12,29 @@ framework:
retry_strategy:
max_retries: 3
multiplier: 2
# Bulk teamer mailings, kept off "async" so that a mailing of several hundred
# recipients cannot delay a password reset. Its own cron worker consumes this
# queue and nothing else; the two never see each other's messages, because the
# doctrine transport filters on the queue_name column. See docs/operations.md
# for both cron entries.
#
# The retry delays are spelled out because the default is one second: with a
# multiplier alone, all three retries of a mail are spent within seconds, so a
# short hiccup at the mail provider would drop a whole mailing into "failed" at
# once - the very thing sending one message per recipient is meant to avoid.
# A minute, three and nine outlive any realistic blip and span several runs of
# a worker that only lives for a few minutes at a time.
mailing:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
options:
queue_name: mailing
use_notify: true
check_delayed_interval: 60000
retry_strategy:
max_retries: 3
delay: 60000
multiplier: 3
max_delay: 900000
failed: 'doctrine://default?queue_name=failed'
sync: 'sync://'
@@ -20,6 +43,9 @@ framework:
Symfony\Component\Notifier\Message\ChatMessage: async
Symfony\Component\Notifier\Message\SmsMessage: async
# only the fan-out, the mails it produces carry an X-Bus-Transport header
App\Message\SendTeamerMailing: mailing
# Route your messages to the transports
# 'App\Message\YourMessage': async
@@ -30,3 +56,13 @@ when@dev:
Symfony\Component\Mailer\Messenger\SendEmailMessage: sync
Symfony\Component\Notifier\Message\ChatMessage: sync
Symfony\Component\Notifier\Message\SmsMessage: sync
App\Message\SendTeamerMailing: sync
when@test:
framework:
messenger:
routing:
Symfony\Component\Mailer\Messenger\SendEmailMessage: sync
Symfony\Component\Notifier\Message\ChatMessage: sync
Symfony\Component\Notifier\Message\SmsMessage: sync
App\Message\SendTeamerMailing: sync
+3 -3
View File
@@ -3,13 +3,13 @@ zenstruck_schedule:
mailer:
service: mailer
default_to: [email protected]
default_from: info@ep-reisen.de
default_to: [email protected]
default_from: team@ep-reisen.de
subject_prefix: "[MyE&P-Team]"
schedule_extensions:
email_on_failure:
to: [email protected]
to: [email protected]
tasks:
- task: app:bpn-import
+20
View File
@@ -9,6 +9,13 @@ parameters:
teamer_inactive_period: '-2 years'
# Messenger transport the mails of a teamer mailing are queued on. An X-Bus-Transport
# header wins over the routing in messenger.yaml (see SendersLocator::getSenders),
# so this is what keeps a mailing off the shared queue - and what has to be switched
# back to "sync" further down, or a mailing would need a running worker to arrive
# in dev and test while every other mail is sent right away.
mailing_bus_transport: 'mailing'
# Dates (MM-DD) on which teamers must re-confirm their personal data.
personal_data_check_deadlines:
- '04-01'
@@ -51,6 +58,7 @@ services:
$xmlExport: '@xml_export.storage'
$xmlDump: '@xml_dump.storage'
$teamerInactivePeriod: '%teamer_inactive_period%'
$mailingBusTransport: '%mailing_bus_transport%'
App\:
resource: '../src/'
@@ -243,3 +251,15 @@ services:
myep_oauth2_url_access_token: '%env(MYEP_OAUTH2_URL_ACCESS_TOKEN)%'
myep_oauth2_url_resource_owner_details: '%env(MYEP_OAUTH2_URL_RESOURCE_OWNER_DETAILS)%'
myep_oauth2_scopes: '%env(csv:MYEP_OAUTH2_SCOPES)%'
# The mails of a mailing are sent right away here, like every other mail, instead of
# waiting for a worker on the dedicated queue. The mailer transport is deliberately not
# switched: a mailing still goes out over MAILING_MAILER_DSN, so the path being exercised
# locally is the one that runs in production.
when@dev:
parameters:
mailing_bus_transport: 'sync'
when@test:
parameters:
mailing_bus_transport: 'sync'