[Mailer] Add RemoteTemplateEmail to send emails rendered from provide… - #64782
#64782Open
Orkin wants to merge 1 commit into
symfony:8.2symfony/symfony:8.2from
Orkin:mailer-remote-templatesOrkin/symfony:mailer-remote-templatesCopy head branch name to clipboard
Open
[Mailer] Add RemoteTemplateEmail to send emails rendered from provide…#64782Orkin wants to merge 1 commit intosymfony:8.2symfony/symfony:8.2from Orkin:mailer-remote-templatesOrkin/symfony:mailer-remote-templatesCopy head branch name to clipboard
Orkin wants to merge 1 commit into
symfony:8.2symfony/symfony:8.2from
Orkin:mailer-remote-templatesOrkin/symfony:mailer-remote-templatesCopy head branch name to clipboard
Conversation
Orkin
force-pushed
the
mailer-remote-templates
branch
from
July 3, 2026 14:38
a174fe7 to
1c279ec
Compare
Orkin
force-pushed
the
mailer-remote-templates
branch
from
July 23, 2026 08:18
1c279ec to
22782d7
Compare
stof
reviewed
Jul 23, 2026
| { | ||
| /** | ||
| * @param string $reference The provider-side reference of the template (an id, uuid, name or alias, depending on the provider) | ||
| * @param array $variables The variables used by the provider to render the template |
Member
There was a problem hiding this comment.
this should define the proper type for the array keys and values (I assume keys are strings here, not sure about values)
|
|
||
| /** | ||
| * @param string|null $template The provider-side reference of the template (an id, uuid, name or alias, depending on the provider) | ||
| * @param array $variables The variables used by the provider to render the template |
Member
There was a problem hiding this comment.
this should also define the proper type
| 8.2 | ||
| --- | ||
|
|
||
| * Add support for sending emails rendered from a remote template via `RemoteTemplateEmail` |
Member
There was a problem hiding this comment.
this changelog entry is confusing IMO, as this is implemented only for one transport of the bridge, not for all of them.
| } | ||
|
|
||
| private function ensureBodyValid(): void | ||
| protected function ensureBodyValid(): void |
Member
There was a problem hiding this comment.
adding a new extension point should probably have a changelog entry in the component.
…r-hosted templates Add a first-class abstraction for emails rendered by the mail provider from a template hosted on its side: * `RemoteTemplateEmail` carries a template reference and its variables, and allows sending without a local text/HTML body; * transports opt in by implementing `RemoteTemplateTransportInterface` and mapping the template to their native API payload; other transports fail with an explicit exception instead of silently dropping the template; * implement the interface in the Brevo, Resend and Sendgrid bridges; * deprecate the "templateid" and "params" magic headers of the Brevo bridge in favor of the new abstraction.
Orkin
force-pushed
the
mailer-remote-templates
branch
from
July 23, 2026 15:17
22782d7 to
12e15e5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Most transactional email providers can render an email from a template hosted on their side, configured with a template reference and a set of variables. Symfony Mailer currently has no abstraction for this, which has led to inconsistent, bridge-specific hacks:
templateidandparamsheaders (which, as noted in [Mailer] Clarify some tests cases for Resend transport #63196 (comment), escaped review on the initial bridge submission);X-MJ-TemplateID/X-MJ-TemplateLanguageheaders;templateparameter.Previous attempts to extend this header-based approach to other bridges (#63196, #63223) were rightfully rejected: repurposing header names inside a bridge creates hidden restrictions and breaks the transport abstraction.
However, the underlying feature is interoperable — virtually every provider with a Symfony bridge exposes the exact same pair
{template reference, variables map}:templateId(int)paramsTemplateID(int)Variablestemplate(name)t:variablestemplate_iddynamic_template_dataTemplateId/TemplateAliasTemplateModeltemplate_namemerge_varstemplate_idpersonalizationtemplate_uuidtemplate_variablesTemplate.TemplateNameTemplateDatatemplate.idtemplate.variablesThis is the same situation that led to
TagHeader/MetadataHeader: one common concept, provider-specific payload mappings. This PR follows that precedent, but without headers.Design
RemoteTemplateEmail extends Emailcarries the template reference (astring; each bridge casts to its native type) and the variables. Since the body is rendered by the provider,ensureValidity()allows sending without a local text/HTML part — and rejects an email that defines both a local body and a template. A local subject remains allowed (providers support overriding it). Serialization is preserved so async sending via Messenger keeps the template.RemoteTemplateTransportInterfaceis a marker interface implemented by transports that support the feature. Each one maps the reference/variables to its native API payload.AbstractTransport::send()throws aLogicExceptionwhen aRemoteTemplateEmailwith a template is sent through a transport that does not implement the interface (SMTP transports, sendmail, not-yet-migrated bridges…). No header name is reserved, nothing is silently dropped.NullTransportimplements the interface sonull://setups keep working.This addresses the concerns raised on the previous attempts:
{reference, variables}pair that all providers share. Anything provider-specific beyond that stays out of scope.What this PR does
RemoteTemplateEmailandRemoteTemplateTransportInterfacetosymfony/mailer, with the fail-fast check inAbstractTransport;Email::ensureBodyValid()fromprivatetoprotectedinsymfony/mime(BC-safe) so the subclass can adjust body validation;template.id/template.variables), Brevo (templateId/params), SendGrid (template_id/dynamic_template_data);templateidandparamsmagic headers of the Brevo bridge in favor of the new abstraction.Out of scope (by design):
Other bridges (Mailjet, Postmark, Mailtrap, MailerSend, Mailgun, SES…) can follow in separate PRs, including SMTP transports of providers whose relay supports templates through documented headers (e.g. Mailjet's
X-MJ-TemplateID).