diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php
index ed6e644a56982..1927a5d98c798 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/mailer_transports.php
@@ -20,6 +20,7 @@
use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory;
use Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetTransportFactory;
use Symfony\Component\Mailer\Bridge\MailPace\Transport\MailPaceTransportFactory;
+use Symfony\Component\Mailer\Bridge\MicrosoftGraph\Transport\MicrosoftGraphTransportFactory;
use Symfony\Component\Mailer\Bridge\OhMySmtp\Transport\OhMySmtpTransportFactory;
use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory;
use Symfony\Component\Mailer\Bridge\Scaleway\Transport\ScalewayTransportFactory;
@@ -78,6 +79,10 @@
->parent('mailer.transport_factory.abstract')
->tag('mailer.transport_factory')
+ ->set('mailer.transport_factory.microsoftgraph', MicrosoftGraphTransportFactory::class)
+ ->parent('mailer.transport_factory.abstract')
+ ->tag('mailer.transport_factory')
+
->set('mailer.transport_factory.postmark', PostmarkTransportFactory::class)
->parent('mailer.transport_factory.abstract')
->tag('mailer.transport_factory')
diff --git a/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/.gitattributes b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/.gitattributes
new file mode 100644
index 0000000000000..84c7add058fb5
--- /dev/null
+++ b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/.gitattributes
@@ -0,0 +1,4 @@
+/Tests export-ignore
+/phpunit.xml.dist export-ignore
+/.gitattributes export-ignore
+/.gitignore export-ignore
diff --git a/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/.gitignore b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/.gitignore
new file mode 100644
index 0000000000000..c49a5d8df5c65
--- /dev/null
+++ b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/CHANGELOG.md b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/CHANGELOG.md
new file mode 100644
index 0000000000000..468c504deecb2
--- /dev/null
+++ b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/CHANGELOG.md
@@ -0,0 +1,7 @@
+CHANGELOG
+=========
+
+7.3.0
+-----
+
+ * Added the bridge
diff --git a/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/Exception/SendMailException.php b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/Exception/SendMailException.php
new file mode 100644
index 0000000000000..816d36331f715
--- /dev/null
+++ b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/Exception/SendMailException.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Bridge\MicrosoftGraph\Exception;
+
+class SendMailException extends \RuntimeException
+{
+}
diff --git a/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/Exception/SenderNotFoundException.php b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/Exception/SenderNotFoundException.php
new file mode 100644
index 0000000000000..6627ec9ec626d
--- /dev/null
+++ b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/Exception/SenderNotFoundException.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Bridge\MicrosoftGraph\Exception;
+
+class SenderNotFoundException extends SendMailException
+{
+}
diff --git a/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/Exception/UnAuthorizedException.php b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/Exception/UnAuthorizedException.php
new file mode 100644
index 0000000000000..2823064923eb8
--- /dev/null
+++ b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/Exception/UnAuthorizedException.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Bridge\MicrosoftGraph\Exception;
+
+class UnAuthorizedException extends SendMailException
+{
+}
diff --git a/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/LICENSE b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/LICENSE
new file mode 100644
index 0000000000000..3ed9f412ce53d
--- /dev/null
+++ b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2023-present Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/README.md b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/README.md
new file mode 100644
index 0000000000000..84f20d22e0b7d
--- /dev/null
+++ b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/README.md
@@ -0,0 +1,54 @@
+Microsoft Graph API Mailer
+==========================
+
+Provides Microsoft Graph API integration for Symfony Mailer.
+
+
+Prerequisites
+-------------
+
+You will need to:
+ * Register an application in your Microsoft Azure portal,
+ * Grant this application the Microsoft Graph `Mail.Send` permission,
+ * Create a secret for that app.
+
+
+Configuration example
+---------------------
+
+```env
+# MAILER
+MAILER_DSN=microsoft+graph://CLIENT_APP_ID:SECRET@default?tenant=TENANT_ID
+```
+
+If you need to use third parties operated or specific regions Microsoft services (China, US Government, etc.), you can specify Auth Endpoint and Graph Endpoint.
+
+```env
+# MAILER e.g. for China
+MAILER_DSN=microsoft+graph://CLIENT_APP_ID:SECRET@login.partner.microsoftonline.cn?tenant=TENANT_ID&graphEndpoint=https://microsoftgraph.chinacloudapi.cn
+```
+
+| | Authentication endpoint | Graph Endpoint |
+|------------------------|------------------------------------------|-----------------------------------------|
+| Global (default) | https://login.microsoftonline.com | https://graph.microsoft.com |
+| US Government L4 | https://login.microsoftonline.us | https://graph.microsoft.us |
+| US Government L5 (DOD) | https://login.microsoftonline.us | https://dod-graph.microsoft.us |
+| China | https://login.partner.microsoftonline.cn | https://microsoftgraph.chinacloudapi.cn |
+
+More details can be found in the Microsoft documentation :
+ * [Auth Endpoints](https://learn.microsoft.com/en-us/entra/identity-platform/authentication-national-cloud#microsoft-entra-authentication-endpoints)
+ * [Graph Endpoints](https://learn.microsoft.com/en-us/graph/deployments#microsoft-graph-and-graph-explorer-service-root-endpoints)
+
+
+Troubleshooting
+---------------
+
+Beware that the sender email address needs to be an address of an account inside your tenant.
+
+Resources
+---------
+
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/Tests/Transport/MicrosoftGraphTransportFactoryTest.php b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/Tests/Transport/MicrosoftGraphTransportFactoryTest.php
new file mode 100644
index 0000000000000..101e99cd7a9c2
--- /dev/null
+++ b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/Tests/Transport/MicrosoftGraphTransportFactoryTest.php
@@ -0,0 +1,101 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Bridge\MicrosoftGraph\Tests\Transport;
+
+use Microsoft\Graph\Core\NationalCloud;
+use Microsoft\Kiota\Authentication\Oauth\ClientCredentialContext;
+use Psr\Log\NullLogger;
+use Symfony\Component\HttpClient\MockHttpClient;
+use Symfony\Component\Mailer\Bridge\MicrosoftGraph\Transport\MicrosoftGraphTransport;
+use Symfony\Component\Mailer\Bridge\MicrosoftGraph\Transport\MicrosoftGraphTransportFactory;
+use Symfony\Component\Mailer\Exception\InvalidArgumentException;
+use Symfony\Component\Mailer\Test\TransportFactoryTestCase;
+use Symfony\Component\Mailer\Transport\Dsn;
+use Symfony\Component\Mailer\Transport\TransportFactoryInterface;
+
+class MicrosoftGraphTransportFactoryTest extends TransportFactoryTestCase
+{
+ protected const TENANT = 'tenantId';
+
+ public function getFactory(): TransportFactoryInterface
+ {
+ return new MicrosoftGraphTransportFactory(null, new MockHttpClient(), new NullLogger());
+ }
+
+ public static function supportsProvider(): iterable
+ {
+ yield [
+ new Dsn('microsoft+graph', 'default'),
+ true,
+ ];
+
+ yield [
+ new Dsn('microsoft+graph', 'example.com'),
+ true,
+ ];
+ }
+
+ public static function createProvider(): iterable
+ {
+ yield [
+ new Dsn('microsoft+graph', 'default', self::USER, self::PASSWORD, null, ['tenant' => self::TENANT]),
+ new MicrosoftGraphTransport(NationalCloud::GLOBAL, new ClientCredentialContext(self::TENANT, self::USER, self::PASSWORD)),
+ ];
+
+ yield [
+ new Dsn('microsoft+graph', 'germany', self::USER, self::PASSWORD, null, ['tenant' => self::TENANT]),
+ new MicrosoftGraphTransport(NationalCloud::GERMANY, new ClientCredentialContext(self::TENANT, self::USER, self::PASSWORD)),
+ ];
+
+ yield [
+ new Dsn('microsoft+graph', 'china', self::USER, self::PASSWORD, null, ['tenant' => self::TENANT]),
+ new MicrosoftGraphTransport(NationalCloud::CHINA, new ClientCredentialContext(self::TENANT, self::USER, self::PASSWORD)),
+ ];
+
+ yield [
+ new Dsn('microsoft+graph', 'us-gov', self::USER, self::PASSWORD, null, ['tenant' => self::TENANT]),
+ new MicrosoftGraphTransport(NationalCloud::US_GOV, new ClientCredentialContext(self::TENANT, self::USER, self::PASSWORD)),
+ ];
+
+ yield [
+ new Dsn('microsoft+graph', 'us-dod', self::USER, self::PASSWORD, null, ['tenant' => self::TENANT]),
+ new MicrosoftGraphTransport(NationalCloud::US_DOD, new ClientCredentialContext(self::TENANT, self::USER, self::PASSWORD)),
+ ];
+ }
+
+ public static function unsupportedSchemeProvider(): iterable
+ {
+ yield [
+ new Dsn('microsoft+smtp', 'default', self::USER, self::PASSWORD),
+ 'The "microsoft+smtp" scheme is not supported; supported schemes for mailer "microsoft graph" are: "microsoft+graph".',
+ ];
+ }
+
+ public static function incompleteDsnProvider(): iterable
+ {
+ yield [new Dsn('microsoft+graph', 'default', self::USER)];
+
+ yield [new Dsn('microsoft+graph', 'default', self::USER, self::PASSWORD)];
+
+ yield [new Dsn('microsoft+graph', 'default', null, self::PASSWORD)];
+
+ yield [new Dsn('microsoft+graph', 'default', null, null)];
+ }
+
+ public function testInvalidDsnHost()
+ {
+ $factory = $this->getFactory();
+
+ $this->expectException(InvalidArgumentException::class);
+ $factory->create(new Dsn('microsoft+graph', 'some-wrong-national-cloud', self::USER, self::PASSWORD, null, ['tenant' => self::TENANT]));
+ }
+}
diff --git a/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/Transport/MicrosoftGraphTransport.php b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/Transport/MicrosoftGraphTransport.php
new file mode 100644
index 0000000000000..f46336139f2f1
--- /dev/null
+++ b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/Transport/MicrosoftGraphTransport.php
@@ -0,0 +1,169 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Bridge\MicrosoftGraph\Transport;
+
+use GuzzleHttp\Psr7\Stream;
+use GuzzleHttp\Psr7\Utils;
+use Microsoft\Graph\Generated\Models\BodyType;
+use Microsoft\Graph\Generated\Models\EmailAddress;
+use Microsoft\Graph\Generated\Models\FileAttachment;
+use Microsoft\Graph\Generated\Models\ItemBody;
+use Microsoft\Graph\Generated\Models\Message;
+use Microsoft\Graph\Generated\Models\ODataErrors\ODataError;
+use Microsoft\Graph\Generated\Models\Recipient;
+use Microsoft\Graph\Generated\Users\Item\SendMail\SendMailPostRequestBody;
+use Microsoft\Graph\GraphServiceClient;
+use Microsoft\Kiota\Authentication\Oauth\ClientCredentialContext;
+use Symfony\Component\Mailer\Bridge\MicrosoftGraph\Exception\SenderNotFoundException;
+use Symfony\Component\Mailer\Bridge\MicrosoftGraph\Exception\SendMailException;
+use Symfony\Component\Mailer\Bridge\MicrosoftGraph\Exception\UnAuthorizedException;
+use Symfony\Component\Mailer\Envelope;
+use Symfony\Component\Mailer\SentMessage;
+use Symfony\Component\Mailer\Transport\TransportInterface;
+use Symfony\Component\Mime\Address;
+use Symfony\Component\Mime\Email;
+use Symfony\Component\Mime\Header\ParameterizedHeader;
+use Symfony\Component\Mime\Part\DataPart;
+use Symfony\Component\Mime\RawMessage;
+
+class MicrosoftGraphTransport implements TransportInterface
+{
+ private GraphServiceClient $graphServiceClient;
+
+ public function __construct(
+ string $nationalCloud,
+ ClientCredentialContext $clientCredentialContext,
+ ) {
+ $this->graphServiceClient = new GraphServiceClient($clientCredentialContext, [], $nationalCloud);
+ }
+
+ public function send(RawMessage $message, ?Envelope $envelope = null): ?SentMessage
+ {
+ $envelope = null !== $envelope ? clone $envelope : Envelope::create($message);
+
+ if (!$message instanceof Email) {
+ throw new SendMailException(\sprintf("This mailer can only handle mails of class '%s' or it's subclasses, instance of '%s' passed", Email::class, $message::class));
+ }
+
+ $this->sendMail($message);
+
+ return new SentMessage($message, $envelope);
+ }
+
+ private function sendMail(Email $message): void
+ {
+ $message = $this->convertEmailToGraphMessage($message);
+ $body = new SendMailPostRequestBody();
+ $body->setMessage($message);
+ // Make sure $senderAddress is the email of an account in the tenant
+ $senderAddress = $message->getFrom()->getEmailAddress()->getAddress();
+
+ try {
+ $this->graphServiceClient->users()->byUserId($senderAddress)->sendMail()->post($body)->wait();
+ } catch (ODataError $error) {
+ if ('ErrorInvalidUser' === $error->getError()->getCode()) {
+ throw new SenderNotFoundException("Sender email address '.".$senderAddress."' could not be found when calling the Graph API. This is usually because the email address doesn't exist in the tenant.", 404, $error);
+ }
+ throw new SendMailException('Something went wrong while sending email.', $error->getCode(), $error);
+ } catch (\Exception $exception) {
+ if ('unauthorized_client' === $exception->getMessage()) {
+ throw new UnAuthorizedException('Unauthorized to send email. Check your credentials.', 401, $exception);
+ }
+ throw new SendMailException('Something went wrong while sending email.', 0, $exception);
+ }
+ }
+
+ private function convertEmailToGraphMessage(Email $source): Message
+ {
+ $message = new Message();
+
+ // From
+ if (0 === \count($source->getFrom())) {
+ throw new SendMailException("Cannot send mail without 'From'.");
+ }
+
+ $message->setFrom(self::convertAddressToGraphRecipient($source->getFrom()[0]));
+
+ // To
+ $message->setToRecipients(array_map(
+ static fn (Address $address) => self::convertAddressToGraphRecipient($address),
+ $source->getTo()
+ ));
+
+ // CC
+ $message->setCcRecipients(array_map(
+ static fn (Address $address) => self::convertAddressToGraphRecipient($address),
+ $source->getCc()
+ ));
+
+ // BCC
+ $message->setBccRecipients(array_map(
+ static fn (Address $address) => self::convertAddressToGraphRecipient($address),
+ $source->getBcc()
+ ));
+
+ // Subject & body
+ $message->setSubject($source->getSubject() ?? '');
+
+ $itemBody = new ItemBody();
+ if ($source->getHtmlBody()) {
+ $itemBody->setContent((string) $source->getHtmlBody());
+ $itemBody->setContentType(new BodyType(BodyType::HTML));
+ } else {
+ $itemBody->setContent((string) $source->getTextBody());
+ $itemBody->setContentType(new BodyType(BodyType::TEXT));
+ }
+
+ $message->setBody($itemBody);
+ $message->setAttachments(array_map(
+ static fn (DataPart $attachment) => self::convertAttachmentGraphAttachment($attachment),
+ $source->getAttachments()
+ ));
+
+ return $message;
+ }
+
+ private static function convertAddressToGraphRecipient(Address $source): Recipient
+ {
+ $recipient = new Recipient();
+ $emailAddress = new EmailAddress();
+ $emailAddress->setAddress($source->getAddress());
+ $emailAddress->setName($source->getName());
+ $recipient->setEmailAddress($emailAddress);
+
+ return $recipient;
+ }
+
+ private static function convertAttachmentGraphAttachment(DataPart $source): FileAttachment
+ {
+ $attachment = new FileAttachment();
+
+ $contentDisposition = $source->getPreparedHeaders()->get('content-disposition');
+ \assert($contentDisposition instanceof ParameterizedHeader);
+ $filename = $contentDisposition->getParameter('filename');
+
+ $fileStream = Utils::streamFor($source->bodyToString());
+ \assert($fileStream instanceof Stream);
+
+ $attachment->setContentBytes($fileStream);
+ $attachment->setContentType($source->getMediaType().'/'.$source->getMediaSubtype());
+ $attachment->setName($filename);
+ $attachment->setODataType('#microsoft.graph.fileAttachment');
+
+ return $attachment;
+ }
+
+ public function __toString(): string
+ {
+ return 'microsoft_graph://oauth_mail';
+ }
+}
diff --git a/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/Transport/MicrosoftGraphTransportFactory.php b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/Transport/MicrosoftGraphTransportFactory.php
new file mode 100644
index 0000000000000..7010f5caa7b9e
--- /dev/null
+++ b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/Transport/MicrosoftGraphTransportFactory.php
@@ -0,0 +1,63 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mailer\Bridge\MicrosoftGraph\Transport;
+
+use Microsoft\Graph\Core\NationalCloud;
+use Microsoft\Kiota\Authentication\Oauth\ClientCredentialContext;
+use Symfony\Component\Mailer\Exception\IncompleteDsnException;
+use Symfony\Component\Mailer\Exception\InvalidArgumentException;
+use Symfony\Component\Mailer\Exception\UnsupportedSchemeException;
+use Symfony\Component\Mailer\Transport\AbstractTransportFactory;
+use Symfony\Component\Mailer\Transport\Dsn;
+use Symfony\Component\Mailer\Transport\TransportInterface;
+
+final class MicrosoftGraphTransportFactory extends AbstractTransportFactory
+{
+ private const CLOUD_MAP = [
+ 'default' => NationalCloud::GLOBAL,
+ 'germany' => NationalCloud::GERMANY,
+ 'china' => NationalCloud::CHINA,
+ 'us-dod' => NationalCloud::US_DOD,
+ 'us-gov' => NationalCloud::US_GOV,
+ ];
+
+ /**
+ * @return string[]
+ */
+ protected function getSupportedSchemes(): array
+ {
+ return ['microsoft+graph'];
+ }
+
+ public function create(Dsn $dsn): TransportInterface
+ {
+ if ('microsoft+graph' !== $dsn->getScheme()) {
+ throw new UnsupportedSchemeException($dsn, 'microsoft graph', $this->getSupportedSchemes());
+ }
+ $tenantId = $dsn->getOption('tenant');
+ if (null === $tenantId) {
+ throw new IncompleteDsnException("Transport 'microsoft+graph' requires the 'tenant' option.");
+ }
+ if (!isset(self::CLOUD_MAP[$dsn->getHost()])) {
+ throw new InvalidArgumentException(\sprintf("Transport 'microsoft+graph' one of these hosts : '%s'", implode(', ', self::CLOUD_MAP)));
+ }
+
+ return new MicrosoftGraphTransport(
+ self::CLOUD_MAP[$dsn->getHost()],
+ new ClientCredentialContext(
+ $tenantId,
+ $this->getUser($dsn),
+ $this->getPassword($dsn)
+ )
+ );
+ }
+}
diff --git a/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/composer.json b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/composer.json
new file mode 100644
index 0000000000000..fce27d3c5cf2b
--- /dev/null
+++ b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/composer.json
@@ -0,0 +1,43 @@
+{
+ "name": "symfony/microsoft-graph-mailer",
+ "type": "symfony-mailer-bridge",
+ "description": "Symfony Microsoft Graph Mailer Bridge",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Kevin Nguyen",
+ "homepage": "https://github.com/nguyenk"
+ },
+ {
+ "name": "The Coding Machine",
+ "homepage": "https://github.com/thecodingmachine"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=8.1",
+ "symfony/mailer": "^5.4|^6.4|^7.0",
+ "microsoft/microsoft-graph": "^2.0.0",
+ "guzzlehttp/psr7": "^2.0"
+ },
+ "require-dev": {
+ "symfony/http-client": "^5.4|^6.4|^7.0"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Mailer\\Bridge\\MicrosoftGraph\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "config": {
+ "allow-plugins": {
+ "php-http/discovery": true
+ }
+ }
+}
diff --git a/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/phpunit.xml.dist b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/phpunit.xml.dist
new file mode 100644
index 0000000000000..c1500a2ddbd42
--- /dev/null
+++ b/src/Symfony/Component/Mailer/Bridge/MicrosoftGraph/phpunit.xml.dist
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+ ./Tests/
+
+
+
+
+
+ ./
+
+
+ ./Tests
+ ./vendor
+
+
+
diff --git a/src/Symfony/Component/Mailer/Transport.php b/src/Symfony/Component/Mailer/Transport.php
index 8543ebbea09d1..2a5aa3803625d 100644
--- a/src/Symfony/Component/Mailer/Transport.php
+++ b/src/Symfony/Component/Mailer/Transport.php
@@ -21,6 +21,7 @@
use Symfony\Component\Mailer\Bridge\MailerSend\Transport\MailerSendTransportFactory;
use Symfony\Component\Mailer\Bridge\Mailgun\Transport\MailgunTransportFactory;
use Symfony\Component\Mailer\Bridge\Mailjet\Transport\MailjetTransportFactory;
+use Symfony\Component\Mailer\Bridge\MicrosoftGraph\Transport\MicrosoftGraphTransportFactory;
use Symfony\Component\Mailer\Bridge\OhMySmtp\Transport\OhMySmtpTransportFactory;
use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory;
use Symfony\Component\Mailer\Bridge\Scaleway\Transport\ScalewayTransportFactory;
@@ -54,6 +55,7 @@ final class Transport
MailgunTransportFactory::class,
MailjetTransportFactory::class,
MandrillTransportFactory::class,
+ MicrosoftGraphTransportFactory::class,
OhMySmtpTransportFactory::class,
PostmarkTransportFactory::class,
ScalewayTransportFactory::class,
@@ -64,14 +66,14 @@ final class Transport
private iterable $factories;
- public static function fromDsn(#[\SensitiveParameter] string $dsn, EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null): TransportInterface
+ public static function fromDsn(#[\SensitiveParameter] string $dsn, ?EventDispatcherInterface $dispatcher = null, ?HttpClientInterface $client = null, ?LoggerInterface $logger = null): TransportInterface
{
$factory = new self(iterator_to_array(self::getDefaultFactories($dispatcher, $client, $logger)));
return $factory->fromString($dsn);
}
- public static function fromDsns(#[\SensitiveParameter] array $dsns, EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null): TransportInterface
+ public static function fromDsns(#[\SensitiveParameter] array $dsns, ?EventDispatcherInterface $dispatcher = null, ?HttpClientInterface $client = null, ?LoggerInterface $logger = null): TransportInterface
{
$factory = new self(iterator_to_array(self::getDefaultFactories($dispatcher, $client, $logger)));
@@ -142,7 +144,7 @@ private function parseDsn(#[\SensitiveParameter] string $dsn, int $offset = 0):
}
if (preg_match('{(\w+)\(}A', $dsn, $matches, 0, $offset)) {
- throw new InvalidArgumentException(sprintf('The "%s" keyword is not valid (valid ones are "%s"), ', $matches[1], implode('", "', array_keys($keywords))));
+ throw new InvalidArgumentException(\sprintf('The "%s" keyword is not valid (valid ones are "%s"), ', $matches[1], implode('", "', array_keys($keywords))));
}
if ($pos = strcspn($dsn, ' )', $offset)) {
@@ -167,7 +169,7 @@ public function fromDsnObject(Dsn $dsn): TransportInterface
/**
* @return \Traversable
*/
- public static function getDefaultFactories(EventDispatcherInterface $dispatcher = null, HttpClientInterface $client = null, LoggerInterface $logger = null): \Traversable
+ public static function getDefaultFactories(?EventDispatcherInterface $dispatcher = null, ?HttpClientInterface $client = null, ?LoggerInterface $logger = null): \Traversable
{
foreach (self::FACTORY_CLASSES as $factoryClass) {
if (class_exists($factoryClass)) {