Skip to content

Commit 24b5ddd

Browse files
committed
Added handling embedded/inline images
1 parent 96b86a1 commit 24b5ddd

File tree

5 files changed

+182
-6
lines changed

5 files changed

+182
-6
lines changed

src/MicrosoftGraphTransport.php

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,23 @@ protected function doSend(SentMessage $message): void
3434
$email = MessageConverter::toEmail($message->getOriginalMessage());
3535
$envelope = $message->getEnvelope();
3636

37+
$html = $email->getHtmlBody();
38+
39+
[$attachments, $html] = $this->prepareAttachments($email, $html);
40+
3741
$payload = [
3842
'message' => [
3943
'subject' => $email->getSubject(),
4044
'body' => [
41-
'contentType' => $email->getHtmlBody() === null ? 'Text' : 'HTML',
42-
'content' => $email->getHtmlBody() ?: $email->getTextBody(),
45+
'contentType' => $html === null ? 'Text' : 'HTML',
46+
'content' => $html ?: $email->getTextBody(),
4347
],
4448
'toRecipients' => $this->transformEmailAddresses($this->getRecipients($email, $envelope)),
4549
'ccRecipients' => $this->transformEmailAddresses(collect($email->getCc())),
4650
'bccRecipients' => $this->transformEmailAddresses(collect($email->getBcc())),
4751
'replyTo' => $this->transformEmailAddresses(collect($email->getReplyTo())),
4852
'sender' => $this->transformEmailAddress($envelope->getSender()),
49-
'attachments' => $this->getAttachments($email),
53+
'attachments' => $attachments,
5054
],
5155
'saveToSentItems' => config('mail.mailers.microsoft-graph.save_to_sent_items', false),
5256
];
@@ -89,19 +93,28 @@ protected function getRecipients(Email $email, Envelope $envelope): Collection
8993
->filter(fn (Address $address) => !in_array($address, array_merge($email->getCc(), $email->getBcc()), true));
9094
}
9195

92-
protected function getAttachments(Email $email): array
96+
/**
97+
* @param Email $email
98+
* @param string|null $html
99+
* @return array
100+
*/
101+
protected function prepareAttachments(Email $email, ?string $html): array
93102
{
94103
$attachments = [];
95104
foreach ($email->getAttachments() as $attachment) {
96-
$fileName = $attachment->getPreparedHeaders()->getHeaderParameter('Content-Disposition', 'filename');
105+
$headers = $attachment->getPreparedHeaders();
106+
$fileName = $headers->getHeaderParameter('Content-Disposition', 'filename');
107+
97108
$attachments[] = [
98109
'@odata.type' => '#microsoft.graph.fileAttachment',
99110
'name' => $fileName,
100111
'contentType' => $attachment->getMediaType(),
101112
'contentBytes' => base64_encode($attachment->getBody()),
113+
'contentId' => $fileName,
114+
'isInline' => $headers->getHeaderBody('Content-Disposition') === 'inline',
102115
];
103116
}
104117

105-
return $attachments;
118+
return [$attachments, $html];
106119
}
107120
}

tests/MicrosoftGraphTransportTest.php

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Illuminate\Support\Str;
99
use InnoGE\LaravelMsGraphMail\Exceptions\ConfigurationMissing;
1010
use InnoGE\LaravelMsGraphMail\Tests\Stubs\TestMail;
11+
use InnoGE\LaravelMsGraphMail\Tests\Stubs\TestMailWithInlineImage;
1112

1213
it('sends html mails with microsoft graph', function () {
1314
Config::set('mail.mailers.microsoft-graph', [
@@ -75,12 +76,16 @@
7576
'name' => 'test-file-1.txt',
7677
'contentType' => 'text',
7778
'contentBytes' => 'Zm9vCg==',
79+
'contentId' => 'test-file-1.txt',
80+
'isInline' => false,
7881
],
7982
[
8083
'@odata.type' => '#microsoft.graph.fileAttachment',
8184
'name' => 'test-file-2.txt',
8285
'contentType' => 'text',
8386
'contentBytes' => 'Zm9vCg==',
87+
'contentId' => 'test-file-2.txt',
88+
'isInline' => false,
8489
],
8590
],
8691
],
@@ -157,12 +162,16 @@
157162
'name' => 'test-file-1.txt',
158163
'contentType' => 'text',
159164
'contentBytes' => 'Zm9vCg==',
165+
'contentId' => 'test-file-1.txt',
166+
'isInline' => false,
160167
],
161168
[
162169
'@odata.type' => '#microsoft.graph.fileAttachment',
163170
'name' => 'test-file-2.txt',
164171
'contentType' => 'text',
165172
'contentBytes' => 'Zm9vCg==',
173+
'contentId' => 'test-file-2.txt',
174+
'isInline' => false,
166175
],
167176
],
168177
],
@@ -272,3 +281,94 @@
272281
'The mail from address is missing from the configuration file.',
273282
],
274283
]);
284+
285+
it('sends html mails with inline images with microsoft graph', function () {
286+
Config::set('mail.mailers.microsoft-graph', [
287+
'transport' => 'microsoft-graph',
288+
'client_id' => 'foo_client_id',
289+
'client_secret' => 'foo_client_secret',
290+
'tenant_id' => 'foo_tenant_id',
291+
'from' => [
292+
'address' => '[email protected]',
293+
'name' => 'Taylor Otwell',
294+
],
295+
]);
296+
Config::set('mail.default', 'microsoft-graph');
297+
Config::set('filesystems.default', 'local');
298+
Config::set('filesystems.disks.local.root', realpath(__DIR__.'/Resources/files'));
299+
300+
Cache::set('microsoft-graph-api-access-token', 'foo_access_token', 3600);
301+
302+
Http::fake();
303+
304+
Mail::to('[email protected]')
305+
306+
307+
->send(new TestMailWithInlineImage());
308+
309+
Http::assertSent(function (Request $value) {
310+
// ContentId gets random generated, so get this value first and check for equality later
311+
$inlineImageContentId = json_decode($value->body())->message->attachments[1]->contentId;
312+
313+
expect($value)
314+
->url()->toBe('https://graph.microsoft.com/v1.0/users/[email protected]/sendMail')
315+
->hasHeader('Authorization', 'Bearer foo_access_token')->toBeTrue()
316+
->body()->json()->toBe([
317+
'message' => [
318+
'subject' => 'Dev Test',
319+
'body' => [
320+
'contentType' => 'HTML',
321+
'content' => '<b>Test</b><img src="cid:' . $inlineImageContentId . '">'.PHP_EOL,
322+
],
323+
'toRecipients' => [
324+
[
325+
'emailAddress' => [
326+
'address' => '[email protected]',
327+
],
328+
],
329+
],
330+
'ccRecipients' => [
331+
[
332+
'emailAddress' => [
333+
'address' => '[email protected]',
334+
],
335+
],
336+
],
337+
'bccRecipients' => [
338+
[
339+
'emailAddress' => [
340+
'address' => '[email protected]',
341+
],
342+
],
343+
],
344+
'replyTo' => [],
345+
'sender' => [
346+
'emailAddress' => [
347+
'address' => '[email protected]',
348+
],
349+
],
350+
'attachments' => [
351+
[
352+
'@odata.type' => '#microsoft.graph.fileAttachment',
353+
'name' => 'test-file-1.txt',
354+
'contentType' => 'text',
355+
'contentBytes' => 'Zm9vCg==',
356+
'contentId' => 'test-file-1.txt',
357+
'isInline' => false,
358+
],
359+
[
360+
'@odata.type' => '#microsoft.graph.fileAttachment',
361+
'name' => $inlineImageContentId,
362+
'contentType' => 'image',
363+
'contentBytes' => '/9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wgARCABLAGQDAREAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAj/xAAWAQEBAQAAAAAAAAAAAAAAAAAABQj/2gAMAwEAAhADEAAAAZ71TDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/xAAUEAEAAAAAAAAAAAAAAAAAAABw/9oACAEBAAEFAgL/xAAUEQEAAAAAAAAAAAAAAAAAAABw/9oACAEDAQE/AQL/xAAUEQEAAAAAAAAAAAAAAAAAAABw/9oACAECAQE/AQL/xAAUEAEAAAAAAAAAAAAAAAAAAABw/9oACAEBAAY/AgL/xAAUEAEAAAAAAAAAAAAAAAAAAABw/9oACAEBAAE/IQL/2gAMAwEAAgADAAAAEEkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkv/xAAUEQEAAAAAAAAAAAAAAAAAAABw/9oACAEDAQE/EAL/xAAUEQEAAAAAAAAAAAAAAAAAAABw/9oACAECAQE/EAL/xAAUEAEAAAAAAAAAAAAAAAAAAABw/9oACAEBAAE/EAL/2Q==',
364+
'contentId' => $inlineImageContentId,
365+
'isInline' => true,
366+
],
367+
],
368+
],
369+
'saveToSentItems' => false,
370+
]);
371+
372+
return true;
373+
});
374+
});

tests/Resources/files/blue.jpg

640 Bytes
Loading
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<b>Test</b><img src="{{ $message->embed(\Illuminate\Support\Facades\Storage::path('blue.jpg')) }}">
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
namespace InnoGE\LaravelMsGraphMail\Tests\Stubs;
4+
5+
use Illuminate\Bus\Queueable;
6+
use Illuminate\Mail\Attachment;
7+
use Illuminate\Mail\Mailable;
8+
use Illuminate\Mail\Mailables\Content;
9+
use Illuminate\Mail\Mailables\Envelope;
10+
use Illuminate\Queue\SerializesModels;
11+
12+
class TestMailWithInlineImage extends Mailable
13+
{
14+
use Queueable, SerializesModels;
15+
16+
/**
17+
* Create a new message instance.
18+
*
19+
* @return void
20+
*/
21+
public function __construct(private readonly bool $isHtml = true)
22+
{
23+
}
24+
25+
/**
26+
* Get the message envelope.
27+
*
28+
* @return \Illuminate\Mail\Mailables\Envelope
29+
*/
30+
public function envelope()
31+
{
32+
return new Envelope(
33+
subject: 'Dev Test',
34+
);
35+
}
36+
37+
/**
38+
* Get the message content definition.
39+
*
40+
* @return \Illuminate\Mail\Mailables\Content
41+
*/
42+
public function content()
43+
{
44+
if (! $this->isHtml) {
45+
return new Content(text: 'text-mail');
46+
}
47+
48+
return new Content(html: 'html-mail-with-inline-image');
49+
}
50+
51+
/**
52+
* Get the attachments for the message.
53+
*
54+
* @return array
55+
*/
56+
public function attachments(): array
57+
{
58+
return [
59+
Attachment::fromPath('tests/Resources/files/test-file-1.txt'),
60+
];
61+
}
62+
}

0 commit comments

Comments
 (0)