From 51aa16df31df95d2440bcdb6f40d3c5263fa34f1 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 1 Apr 2026 16:51:39 +0530 Subject: [PATCH 1/2] feat: add attachment support to Resend email adapter Resend's API supports attachments with base64-encoded content. Remove the exception that blocked attachments and add proper handling with MAX_ATTACHMENT_BYTES (25MB) validation, matching the SMTP adapter. --- src/Utopia/Messaging/Adapter/Email/Resend.php | 32 ++++++++- tests/Messaging/Adapter/Email/ResendTest.php | 65 +++++++++++++++---- 2 files changed, 82 insertions(+), 15 deletions(-) diff --git a/src/Utopia/Messaging/Adapter/Email/Resend.php b/src/Utopia/Messaging/Adapter/Email/Resend.php index c0dfb414..399cc145 100644 --- a/src/Utopia/Messaging/Adapter/Email/Resend.php +++ b/src/Utopia/Messaging/Adapter/Email/Resend.php @@ -35,9 +35,33 @@ public function getMaxMessagesPerRequest(): int */ protected function process(EmailMessage $message): array { - // Resend doesn't support attachments yet + $attachments = []; if (! \is_null($message->getAttachments()) && ! empty($message->getAttachments())) { - throw new \Exception('Resend does not support attachments at this time'); + $size = 0; + foreach ($message->getAttachments() as $attachment) { + if ($attachment->getContent() !== null) { + $size += \strlen($attachment->getContent()); + } else { + $size += \filesize($attachment->getPath()); + } + } + + if ($size > self::MAX_ATTACHMENT_BYTES) { + throw new \Exception('Total attachment size exceeds '.self::MAX_ATTACHMENT_BYTES.' bytes'); + } + + foreach ($message->getAttachments() as $attachment) { + if ($attachment->getContent() !== null) { + $content = \base64_encode($attachment->getContent()); + } else { + $content = \base64_encode(\file_get_contents($attachment->getPath())); + } + + $attachments[] = [ + 'filename' => $attachment->getName(), + 'content' => $content, + ]; + } } $response = new Response($this->getType()); @@ -78,6 +102,10 @@ protected function process(EmailMessage $message): array } } + if (! empty($attachments)) { + $email['attachments'] = $attachments; + } + if (! \is_null($message->getBCC()) && ! empty($message->getBCC())) { $bccList = []; foreach ($message->getBCC() as $bcc) { diff --git a/tests/Messaging/Adapter/Email/ResendTest.php b/tests/Messaging/Adapter/Email/ResendTest.php index 8e8f0234..d5f87741 100644 --- a/tests/Messaging/Adapter/Email/ResendTest.php +++ b/tests/Messaging/Adapter/Email/ResendTest.php @@ -115,22 +115,14 @@ public function testSendMultipleEmails(): void $this->assertEquals('success', $response['results'][1]['status'], \var_export($response, true)); } - public function testSendEmailWithAttachmentsThrowsException(): void + public function testSendEmailWithFileAttachment(): void { - $this->expectException(\Exception::class); - $this->expectExceptionMessage('Resend does not support attachments at this time'); - - $to = $this->testEmail; - $subject = 'Test Subject'; - $content = 'Test Content'; - $fromEmail = $this->testEmail; - $message = new Email( - to: [$to], - subject: $subject, - content: $content, + to: [$this->testEmail], + subject: 'Test File Attachment', + content: 'Test Content with file attachment', fromName: 'Test Sender', - fromEmail: $fromEmail, + fromEmail: $this->testEmail, attachments: [new Attachment( name: 'image.png', path: __DIR__.'/../../../assets/image.png', @@ -138,6 +130,53 @@ public function testSendEmailWithAttachmentsThrowsException(): void )], ); + $response = $this->sender->send($message); + + $this->assertResponse($response); + } + + public function testSendEmailWithStringAttachment(): void + { + $message = new Email( + to: [$this->testEmail], + subject: 'Test String Attachment', + content: 'Test Content with string attachment', + fromName: 'Test Sender', + fromEmail: $this->testEmail, + attachments: [new Attachment( + name: 'test.txt', + path: '', + type: 'text/plain', + content: 'Hello, this is a test attachment.', + )], + ); + + $response = $this->sender->send($message); + + $this->assertResponse($response); + } + + public function testSendEmailWithAttachmentExceedingMaxSize(): void + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Total attachment size exceeds'); + + $largeContent = \str_repeat('x', 25 * 1024 * 1024 + 1); + + $message = new Email( + to: [$this->testEmail], + subject: 'Test Oversized Attachment', + content: 'Test Content', + fromName: 'Test Sender', + fromEmail: $this->testEmail, + attachments: [new Attachment( + name: 'large.bin', + path: '', + type: 'application/octet-stream', + content: $largeContent, + )], + ); + $this->sender->send($message); } } From 9fb92417cf7388713e13dcbe7cf3048acd50a92d Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 1 Apr 2026 16:56:47 +0530 Subject: [PATCH 2/2] fix: address PR review comments - Add error handling for filesize() returning false on unreadable paths - Add error handling for file_get_contents() returning false - Include content_type field in Resend attachment payload --- src/Utopia/Messaging/Adapter/Email/Resend.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Utopia/Messaging/Adapter/Email/Resend.php b/src/Utopia/Messaging/Adapter/Email/Resend.php index 399cc145..7ad0ff69 100644 --- a/src/Utopia/Messaging/Adapter/Email/Resend.php +++ b/src/Utopia/Messaging/Adapter/Email/Resend.php @@ -42,7 +42,11 @@ protected function process(EmailMessage $message): array if ($attachment->getContent() !== null) { $size += \strlen($attachment->getContent()); } else { - $size += \filesize($attachment->getPath()); + $fileSize = \filesize($attachment->getPath()); + if ($fileSize === false) { + throw new \Exception('Failed to read attachment file: '.$attachment->getPath()); + } + $size += $fileSize; } } @@ -54,12 +58,17 @@ protected function process(EmailMessage $message): array if ($attachment->getContent() !== null) { $content = \base64_encode($attachment->getContent()); } else { - $content = \base64_encode(\file_get_contents($attachment->getPath())); + $data = \file_get_contents($attachment->getPath()); + if ($data === false) { + throw new \Exception('Failed to read attachment file: '.$attachment->getPath()); + } + $content = \base64_encode($data); } $attachments[] = [ 'filename' => $attachment->getName(), 'content' => $content, + 'content_type' => $attachment->getType(), ]; } }