Skip to content

Commit a84cc51

Browse files
geisisgarwood
andauthored
Support custom mail headers (#39)
* Support custom mail headers * Implement custom headers * Implement custom headers * Update MicrosoftGraphTransport.php * Update MicrosoftGraphTransportTest.php * refactoring & cleanup --------- Co-authored-by: Seb Garwood <[email protected]> Co-authored-by: Seb Garwood <[email protected]>
1 parent fb1e86e commit a84cc51

File tree

4 files changed

+133
-1
lines changed

4 files changed

+133
-1
lines changed

phpstan-baseline.neon

+10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
parameters:
22
ignoreErrors:
3+
-
4+
message: "#^Method InnoGE\\\\LaravelMsGraphMail\\\\MicrosoftGraphTransport\\:\\:getInternetMessageHeaders\\(\\) return type has no value type specified in iterable type array\\.$#"
5+
count: 1
6+
path: src/MicrosoftGraphTransport.php
7+
38
-
49
message: "#^Method InnoGE\\\\LaravelMsGraphMail\\\\MicrosoftGraphTransport\\:\\:transformEmailAddress\\(\\) return type has no value type specified in iterable type array\\.$#"
510
count: 1
@@ -20,6 +25,11 @@ parameters:
2025
count: 1
2126
path: src/MicrosoftGraphTransport.php
2227

28+
-
29+
message: "#^Unable to resolve the template type TKey in call to function collect$#"
30+
count: 1
31+
path: src/MicrosoftGraphTransport.php
32+
2333
-
2434
message: "#^Method InnoGE\\\\LaravelMsGraphMail\\\\Services\\\\MicrosoftGraphApiService\\:\\:getAccessToken\\(\\) should return string but returns mixed\\.$#"
2535
count: 1

src/MicrosoftGraphTransport.php

+19
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Symfony\Component\Mailer\Transport\AbstractTransport;
1313
use Symfony\Component\Mime\Address;
1414
use Symfony\Component\Mime\Email;
15+
use Symfony\Component\Mime\Header\HeaderInterface;
1516
use Symfony\Component\Mime\MessageConverter;
1617

1718
class MicrosoftGraphTransport extends AbstractTransport
@@ -58,6 +59,10 @@ protected function doSend(SentMessage $message): void
5859
'saveToSentItems' => config('mail.mailers.microsoft-graph.save_to_sent_items', false) ?? false,
5960
];
6061

62+
if (filled($headers = $this->getInternetMessageHeaders($email))) {
63+
$payload['message']['internetMessageHeaders'] = $headers;
64+
}
65+
6166
$this->microsoftGraphApiService->sendMail($envelope->getSender()->getAddress(), $payload);
6267
}
6368

@@ -112,4 +117,18 @@ protected function getRecipients(Email $email, Envelope $envelope): Collection
112117
return collect($envelope->getRecipients())
113118
->filter(fn (Address $address) => ! in_array($address, array_merge($email->getCc(), $email->getBcc()), true));
114119
}
120+
121+
/**
122+
* Transforms given Symfony Headers
123+
* to Microsoft Graph internet message headers
124+
* see https://learn.microsoft.com/en-us/graph/api/resources/internetmessageheader?view=graph-rest-1.0
125+
*/
126+
protected function getInternetMessageHeaders(Email $email): ?array
127+
{
128+
return collect($email->getHeaders()->all())
129+
->filter(fn (HeaderInterface $header) => str_starts_with($header->getName(), 'X-'))
130+
->map(fn (HeaderInterface $header) => ['name' => $header->getName(), 'value' => $header->getBodyAsString()])
131+
->values()
132+
->all() ?: null;
133+
}
115134
}

tests/MicrosoftGraphTransportTest.php

+91
Original file line numberDiff line numberDiff line change
@@ -520,3 +520,94 @@
520520
return true;
521521
});
522522
});
523+
524+
it('sends custom mail headers with microsoft graph', function () {
525+
Config::set('mail.mailers.microsoft-graph', [
526+
'transport' => 'microsoft-graph',
527+
'client_id' => 'foo_client_id',
528+
'client_secret' => 'foo_client_secret',
529+
'tenant_id' => 'foo_tenant_id',
530+
'from' => [
531+
'address' => '[email protected]',
532+
'name' => 'Taylor Otwell',
533+
],
534+
'save_to_sent_items' => null,
535+
]);
536+
Config::set('mail.default', 'microsoft-graph');
537+
538+
Cache::set('microsoft-graph-api-access-token', 'foo_access_token', 3600);
539+
540+
Http::fake();
541+
542+
Mail::to('[email protected]')
543+
544+
545+
->send(new TestMail(includeHeaders: true));
546+
547+
Http::assertSent(function (Request $value) {
548+
expect($value)
549+
->url()->toBe('https://graph.microsoft.com/v1.0/users/[email protected]/sendMail')
550+
->hasHeader('Authorization', 'Bearer foo_access_token')->toBeTrue()
551+
->body()->json()->toBe([
552+
'message' => [
553+
'subject' => 'Dev Test',
554+
'body' => [
555+
'contentType' => 'HTML',
556+
'content' => '<b>Test</b>'.PHP_EOL,
557+
],
558+
'toRecipients' => [
559+
[
560+
'emailAddress' => [
561+
'address' => '[email protected]',
562+
],
563+
],
564+
],
565+
'ccRecipients' => [
566+
[
567+
'emailAddress' => [
568+
'address' => '[email protected]',
569+
],
570+
],
571+
],
572+
'bccRecipients' => [
573+
[
574+
'emailAddress' => [
575+
'address' => '[email protected]',
576+
],
577+
],
578+
],
579+
'replyTo' => [],
580+
'sender' => [
581+
'emailAddress' => [
582+
'address' => '[email protected]',
583+
],
584+
],
585+
'attachments' => [
586+
[
587+
'@odata.type' => '#microsoft.graph.fileAttachment',
588+
'name' => 'test-file-1.txt',
589+
'contentType' => 'text',
590+
'contentBytes' => 'Zm9vCg==',
591+
'contentId' => 'test-file-1.txt',
592+
'isInline' => false,
593+
],
594+
[
595+
'@odata.type' => '#microsoft.graph.fileAttachment',
596+
'name' => 'test-file-2.txt',
597+
'contentType' => 'text',
598+
'contentBytes' => 'Zm9vCg==',
599+
'contentId' => 'test-file-2.txt',
600+
'isInline' => false,
601+
],
602+
],
603+
'internetMessageHeaders' => [[
604+
'name' => 'X-Custom-Header',
605+
'value' => 'Custom Header',
606+
]],
607+
],
608+
'saveToSentItems' => false,
609+
]);
610+
611+
return true;
612+
});
613+
});

tests/Stubs/TestMail.php

+13-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Illuminate\Mail\Mailable;
88
use Illuminate\Mail\Mailables\Content;
99
use Illuminate\Mail\Mailables\Envelope;
10+
use Illuminate\Mail\Mailables\Headers;
1011
use Illuminate\Queue\SerializesModels;
1112

1213
class TestMail extends Mailable
@@ -18,7 +19,7 @@ class TestMail extends Mailable
1819
*
1920
* @return void
2021
*/
21-
public function __construct(private readonly bool $isHtml = true) {}
22+
public function __construct(private readonly bool $isHtml = true, private readonly bool $includeHeaders = false) {}
2223

2324
/**
2425
* Get the message envelope.
@@ -56,4 +57,15 @@ public function attachments(): array
5657
Attachment::fromPath('tests/Resources/files/test-file-2.txt'),
5758
];
5859
}
60+
61+
public function headers(): Headers
62+
{
63+
if ($this->includeHeaders) {
64+
return new Headers(text: [
65+
'X-Custom-Header' => 'Custom Header',
66+
]);
67+
}
68+
69+
return new Headers;
70+
}
5971
}

0 commit comments

Comments
 (0)