Title
Support a second Mailer transport dedicated to mass mailing (mailer_dsn_massmailing)
Context / Problem
Chamilo currently uses a single Symfony Mailer transport (MAILER_DSN, assembled from parameters stored in the settings table) for all outgoing email — both automated transactional notifications (agenda reminders, session notifications, etc.) and mass mailings (course/session announcements broadcast to a large number of users).
Routing both types of email through the same transport creates a deliverability risk: a mass-mailing spike (an announcement broadcast to several thousand users) can degrade sending reputation and delay or cause failures for urgent transactional notifications sent through the same relay at the same time. This has historically been the case, for example, con campus.chamilo.org before we switched it (for this reason, mainly) to campus.chamilo.net.
The mass mailing actions (course/global announcements, agenda updates) are secondary to the transactional mailing actions (password recovery, assignment hand-out notification, etc), though, so if any mailer is considered the "main" one, it's the transactional one.
Goal: allow an optional second Mailer DSN to be configured, dedicated to mass mailing and distinct from the main DSN used for transactional email, without making this configuration mandatory (full backward compatibility when not set).
Expected behavior
- A new platform setting,
mailer_dsn_massmailing, is added (likely under the same category as the existing setting(s) that make up the main DSN — to be identified/confirmed by a maintainer, see "Points to confirm" below).
- If
mailer_dsn_massmailing is not set, or its value is strictly identical to the main DSN: behavior is unchanged, a single transport is used (backward compatible by default — no extra configuration required for existing instances).
- If
mailer_dsn_massmailing is set and differs from the main DSN: Symfony Mailer is configured with two named transports (main and massmailing), and mass-mailing sends (announcements, large-scale agenda broadcasts) are explicitly routed to the massmailing transport via the X-Transport header, while transactional notifications keep using the main transport by default.
Proposed technical approach
1. Symfony Mailer configuration (config/packages/mailer.yaml)
Replace the single dsn entry with a transports entry, following Symfony Mailer's official multi-transport mechanism:
framework:
mailer:
transports:
main: '%env(resolve:MAILER_DSN)%'
massmailing: '%env(resolve:MAILER_DSN_MASSMAILING)%'
The second environment variable (MAILER_DSN_MASSMAILING) needs to be resolved dynamically from the settings table, following whatever mechanism is already used for MAILER_DSN today — to be identified precisely in the existing code (see "Points to confirm"). If that mechanism is a custom EnvVarProcessorInterface, it should either be extended to accept a second prefix/parameter, or duplicated for mailer_dsn_massmailing, returning the main DSN's value as the default when mailer_dsn_massmailing is not configured (to satisfy point 2 above).
2. New platform setting
Per convention, this must be added in two steps, not one:
- Add the variable to the relevant
*SettingsSchema.php file (the one that already declares mailer_dsn/the existing SMTP parameters), with an empty default value and the appropriate form type (text field, noting that it's an optional Symfony Mailer DSN).
- Add the corresponding entry to
SettingsCurrentFixtures::getNewConfigurationSettings() (src/CoreBundle/DataFixtures/SettingsCurrentFixtures.php), under the same category as the existing setting.
- Check whether a fixtures-upsert migration already exists for the current version (most recent
src/CoreBundle/Migrations/Schema/V2xx/ directory — the exact version number should be checked at implementation time). If such a migration already exists for this version, do not create a second one: just add the fixture entry (step 2); ops teams re-run the existing migration manually after upgrading. If no such migration exists yet for the current version, create a new one by copying the body of the latest fixtures-upsert migration (e.g. Version20250926174000.php, to be verified) into a freshly dated class.
3. Routing mass-mailing sends to the massmailing transport
Locate the relevant sending path(s) — presumably the service responsible for broadcasting course/session announcements ("Announcements"), and, if applicable, mass agenda broadcasts (as distinct from individual transactional reminders like "J-1 reminders"). On these sends specifically:
if ($massMailingDsn !== null && $massMailingDsn !== $mainDsn) {
$email->getHeaders()->addTextHeader('X-Transport', 'massmailing');
}
Transactional sends (individual reminders, cancellation notifications, etc.) are left unchanged and keep using the default (main) transport.
Caveat flagged by the Symfony community: several reports (see symfony/symfony#46372) indicate that the X-Transport header is not always honored reliably when sending goes through Messenger (asynchronous queue processing) rather than a direct synchronous send. If our announcement sending goes through Messenger, this deserves a dedicated test before considering the feature reliable.
Tests
- Unit/functional test verifying that when
mailer_dsn_massmailing is absent (or identical to the main DSN), a single transport is used and sending behavior is strictly identical to today.
- Test verifying that an email sent through the "announcements" path with
mailer_dsn_massmailing set and different from the main DSN carries the X-Transport: massmailing header before sending (and that it is correctly stripped from the final message, per Symfony Mailer's standard behavior).
- If
mailer_dsn_massmailing is exposed as a field under Administration > Settings, extend the existing Behat scenario for that page (editing a setting) rather than creating a new one, per the repository's rule requiring Behat coverage for any new interface.
Title
Support a second Mailer transport dedicated to mass mailing (
mailer_dsn_massmailing)Context / Problem
Chamilo currently uses a single Symfony Mailer transport (
MAILER_DSN, assembled from parameters stored in thesettingstable) for all outgoing email — both automated transactional notifications (agenda reminders, session notifications, etc.) and mass mailings (course/session announcements broadcast to a large number of users).Routing both types of email through the same transport creates a deliverability risk: a mass-mailing spike (an announcement broadcast to several thousand users) can degrade sending reputation and delay or cause failures for urgent transactional notifications sent through the same relay at the same time. This has historically been the case, for example, con campus.chamilo.org before we switched it (for this reason, mainly) to campus.chamilo.net.
The mass mailing actions (course/global announcements, agenda updates) are secondary to the transactional mailing actions (password recovery, assignment hand-out notification, etc), though, so if any mailer is considered the "main" one, it's the transactional one.
Goal: allow an optional second Mailer DSN to be configured, dedicated to mass mailing and distinct from the main DSN used for transactional email, without making this configuration mandatory (full backward compatibility when not set).
Expected behavior
mailer_dsn_massmailing, is added (likely under the same category as the existing setting(s) that make up the main DSN — to be identified/confirmed by a maintainer, see "Points to confirm" below).mailer_dsn_massmailingis not set, or its value is strictly identical to the main DSN: behavior is unchanged, a single transport is used (backward compatible by default — no extra configuration required for existing instances).mailer_dsn_massmailingis set and differs from the main DSN: Symfony Mailer is configured with two named transports (mainandmassmailing), and mass-mailing sends (announcements, large-scale agenda broadcasts) are explicitly routed to themassmailingtransport via theX-Transportheader, while transactional notifications keep using themaintransport by default.Proposed technical approach
1. Symfony Mailer configuration (
config/packages/mailer.yaml)Replace the single
dsnentry with atransportsentry, following Symfony Mailer's official multi-transport mechanism:The second environment variable (
MAILER_DSN_MASSMAILING) needs to be resolved dynamically from thesettingstable, following whatever mechanism is already used forMAILER_DSNtoday — to be identified precisely in the existing code (see "Points to confirm"). If that mechanism is a customEnvVarProcessorInterface, it should either be extended to accept a second prefix/parameter, or duplicated formailer_dsn_massmailing, returning the main DSN's value as the default whenmailer_dsn_massmailingis not configured (to satisfy point 2 above).2. New platform setting
Per convention, this must be added in two steps, not one:
*SettingsSchema.phpfile (the one that already declaresmailer_dsn/the existing SMTP parameters), with an empty default value and the appropriate form type (text field, noting that it's an optional Symfony Mailer DSN).SettingsCurrentFixtures::getNewConfigurationSettings()(src/CoreBundle/DataFixtures/SettingsCurrentFixtures.php), under the same category as the existing setting.src/CoreBundle/Migrations/Schema/V2xx/directory — the exact version number should be checked at implementation time). If such a migration already exists for this version, do not create a second one: just add the fixture entry (step 2); ops teams re-run the existing migration manually after upgrading. If no such migration exists yet for the current version, create a new one by copying the body of the latest fixtures-upsert migration (e.g.Version20250926174000.php, to be verified) into a freshly dated class.3. Routing mass-mailing sends to the
massmailingtransportLocate the relevant sending path(s) — presumably the service responsible for broadcasting course/session announcements ("Announcements"), and, if applicable, mass agenda broadcasts (as distinct from individual transactional reminders like "J-1 reminders"). On these sends specifically:
Transactional sends (individual reminders, cancellation notifications, etc.) are left unchanged and keep using the default (
main) transport.Caveat flagged by the Symfony community: several reports (see symfony/symfony#46372) indicate that the
X-Transportheader is not always honored reliably when sending goes through Messenger (asynchronous queue processing) rather than a direct synchronous send. If our announcement sending goes through Messenger, this deserves a dedicated test before considering the feature reliable.Tests
mailer_dsn_massmailingis absent (or identical to the main DSN), a single transport is used and sending behavior is strictly identical to today.mailer_dsn_massmailingset and different from the main DSN carries theX-Transport: massmailingheader before sending (and that it is correctly stripped from the final message, per Symfony Mailer's standard behavior).mailer_dsn_massmailingis exposed as a field under Administration > Settings, extend the existing Behat scenario for that page (editing a setting) rather than creating a new one, per the repository's rule requiring Behat coverage for any new interface.