diff --git a/src/Utopia/Messaging/Adapter/Email/Mailgun.php b/src/Utopia/Messaging/Adapter/Email/Mailgun.php index 4cb4eccc..fda3e29c 100644 --- a/src/Utopia/Messaging/Adapter/Email/Mailgun.php +++ b/src/Utopia/Messaging/Adapter/Email/Mailgun.php @@ -45,6 +45,26 @@ protected function process(EmailMessage $message): string $domain = $this->isEU ? $euDomain : $usDomain; + $body = [ + 'to' => \implode(',', $message->getTo()), + 'from' => "{$message->getFromName()}<{$message->getFromEmail()}>", + 'subject' => $message->getSubject(), + 'text' => $message->isHtml() ? null : $message->getContent(), + 'html' => $message->isHtml() ? $message->getContent() : null, + ]; + + if (! \is_null($message->getCC())) { + foreach ($message->getCC() as $cc) { + $body['cc'] = "{$body['cc']},{$cc['name']}<{$cc['email']}>"; + } + } + + if (! \is_null($message->getBCC())) { + foreach ($message->getBCC() as $bcc) { + $body['bcc'] = "{$body['bcc']},{$bcc['name']}<{$bcc['email']}>"; + } + } + $response = new Response($this->getType()); $result = $this->request( @@ -52,14 +72,9 @@ protected function process(EmailMessage $message): string url: "https://$domain/v3/{$this->domain}/messages", headers: [ 'Authorization: Basic '.base64_encode('api:'.$this->apiKey), + 'h:Reply-To: '."{$message->getReplyToName()}<{$message->getReplyToEmail()}>", ], - body: \http_build_query([ - 'to' => \implode(',', $message->getTo()), - 'from' => $message->getFrom(), - 'subject' => $message->getSubject(), - 'text' => $message->isHtml() ? null : $message->getContent(), - 'html' => $message->isHtml() ? $message->getContent() : null, - ]), + body: \http_build_query($body), ); $statusCode = $result['statusCode']; diff --git a/src/Utopia/Messaging/Adapter/Email/Mock.php b/src/Utopia/Messaging/Adapter/Email/Mock.php index 079700e4..ebf759ed 100644 --- a/src/Utopia/Messaging/Adapter/Email/Mock.php +++ b/src/Utopia/Messaging/Adapter/Email/Mock.php @@ -40,8 +40,8 @@ protected function process(EmailMessage $message): string $mail->Subject = $message->getSubject(); $mail->Body = $message->getContent(); $mail->AltBody = \strip_tags($message->getContent()); - $mail->setFrom($message->getFrom(), 'Utopia'); - $mail->addReplyTo($message->getFrom(), 'Utopia'); + $mail->setFrom($message->getFromEmail(), $message->getFromName()); + $mail->addReplyTo($message->getReplyToEmail(), $message->getReplyToName()); $mail->isHTML($message->isHtml()); foreach ($message->getTo() as $to) { diff --git a/src/Utopia/Messaging/Adapter/Email/Sendgrid.php b/src/Utopia/Messaging/Adapter/Email/Sendgrid.php index 6d06fce9..db6ef4bc 100644 --- a/src/Utopia/Messaging/Adapter/Email/Sendgrid.php +++ b/src/Utopia/Messaging/Adapter/Email/Sendgrid.php @@ -37,6 +37,34 @@ public function getMaxMessagesPerRequest(): int */ protected function process(EmailMessage $message): string { + $personalizations = [ + [ + 'to' => \array_map( + fn ($to) => ['email' => $to], + $message->getTo() + ), + 'subject' => $message->getSubject(), + ], + ]; + + if (! \is_null($message->getCC())) { + foreach ($message->getCC() as $cc) { + $personalizations[0]['cc'][] = [ + 'name' => $cc['name'], + 'email' => $cc['email'], + ]; + } + } + + if (! \is_null($message->getBCC())) { + foreach ($message->getBCC() as $bcc) { + $personalizations[0]['bcc'][] = [ + 'name' => $bcc['name'], + 'email' => $bcc['email'], + ]; + } + } + $response = new Response($this->getType()); $result = $this->request( method: 'POST', @@ -46,17 +74,14 @@ protected function process(EmailMessage $message): string 'Content-Type: application/json', ], body: \json_encode([ - 'personalizations' => [ - [ - 'to' => \array_map( - fn ($to) => ['email' => $to], - $message->getTo() - ), - 'subject' => $message->getSubject(), - ], + 'personalizations' => $personalizations, + 'reply_to' => [ + 'name' => $message->getReplyToName(), + 'email' => $message->getReplyToEmail(), ], 'from' => [ - 'email' => $message->getFrom(), + 'name' => $message->getFromName(), + 'email' => $message->getFromEmail(), ], 'content' => [ [ diff --git a/src/Utopia/Messaging/Messages/Email.php b/src/Utopia/Messaging/Messages/Email.php index 8e79c10c..268a4dde 100644 --- a/src/Utopia/Messaging/Messages/Email.php +++ b/src/Utopia/Messaging/Messages/Email.php @@ -10,18 +10,53 @@ class Email implements Message * @param array $to The recipients of the email. * @param string $subject The subject of the email. * @param string $content The content of the email. - * @param string|null $from The sender of the email. + * @param string $fromName The name of the sender. + * @param string $fromEmail The email address of the sender. + * @param array>|null $cc . The CC recipients of the email. Each recipient should be an array containing a "name" and an "email" key. + * @param array>|null $bcc . The BCC recipients of the email. Each recipient should be an array containing a "name" and an "email" key. + * @param string|null $replyToName The name of the reply to. + * @param string|null $replyToEmail The email address of the reply to. * @param array|null $attachments The attachments of the email. * @param bool $html Whether the message is HTML or not. + * + * @throws \InvalidArgumentException */ public function __construct( private array $to, private string $subject, private string $content, - private ?string $from = null, + private string $fromName, + private string $fromEmail, + private ?string $replyToName = null, + private ?string $replyToEmail = null, + private ?array $cc = null, + private ?array $bcc = null, private ?array $attachments = null, private bool $html = false ) { + if (\is_null($this->replyToName)) { + $this->replyToName = $this->fromName; + } + + if (\is_null($this->replyToEmail)) { + $this->replyToEmail = $this->fromEmail; + } + + if (! \is_null($this->cc)) { + foreach ($this->cc as $recipient) { + if (! isset($recipient['name']) || ! isset($recipient['email'])) { + throw new \InvalidArgumentException('Each recipient in cc must have a name and email'); + } + } + } + + if (! \is_null($this->bcc)) { + foreach ($this->bcc as $recipient) { + if (! isset($recipient['name']) || ! isset($recipient['email'])) { + throw new \InvalidArgumentException('Each recipient in bcc must have a name and email'); + } + } + } } /** @@ -42,9 +77,40 @@ public function getContent(): string return $this->content; } - public function getFrom(): ?string + public function getFromName(): string + { + return $this->fromName; + } + + public function getFromEmail(): string + { + return $this->fromEmail; + } + + public function getReplyToName(): string + { + return $this->replyToName; + } + + public function getReplyToEmail(): string + { + return $this->replyToEmail; + } + + /** + * @return array>|null + */ + public function getCC(): ?array + { + return $this->cc; + } + + /** + * @return array>|null + */ + public function getBCC(): ?array { - return $this->from; + return $this->bcc; } /** diff --git a/src/Utopia/Messaging/Response.php b/src/Utopia/Messaging/Response.php index 6372ab58..3b929151 100644 --- a/src/Utopia/Messaging/Response.php +++ b/src/Utopia/Messaging/Response.php @@ -78,4 +78,16 @@ public function toArray(): array 'results' => $this->results, ]; } + + /** + * @param array> $results + */ + public function fromArray(array $results): self + { + $response = new self($this->type); + $response->deliveredTo = $this->deliveredTo; + $response->results = $this->results; + + return $response; + } } diff --git a/tests/Messaging/Adapter/Email/EmailTest.php b/tests/Messaging/Adapter/Email/EmailTest.php index 25069992..1b168c2d 100644 --- a/tests/Messaging/Adapter/Email/EmailTest.php +++ b/tests/Messaging/Adapter/Email/EmailTest.php @@ -15,13 +15,15 @@ public function testSendEmail(): void $to = 'tester@localhost.test'; $subject = 'Test Subject'; $content = 'Test Content'; - $from = 'sender@localhost.test'; + $fromName = 'Test Sender'; + $fromEmail = 'sender@localhost.test'; $message = new Email( to: [$to], subject: $subject, content: $content, - from: $from + fromName: $fromName, + fromEmail: $fromEmail, ); $response = \json_decode($sender->send($message), true); @@ -30,7 +32,7 @@ public function testSendEmail(): void $this->assertResponse($response); $this->assertEquals($to, $lastEmail['to'][0]['address']); - $this->assertEquals($from, $lastEmail['from'][0]['address']); + $this->assertEquals($fromEmail, $lastEmail['from'][0]['address']); $this->assertEquals($subject, $lastEmail['subject']); $this->assertEquals($content, \trim($lastEmail['text'])); } diff --git a/tests/Messaging/Adapter/Email/MailgunTest.php b/tests/Messaging/Adapter/Email/MailgunTest.php index 664ceadb..128c490a 100644 --- a/tests/Messaging/Adapter/Email/MailgunTest.php +++ b/tests/Messaging/Adapter/Email/MailgunTest.php @@ -22,13 +22,14 @@ public function testSendEmail(): void $to = \getenv('TEST_EMAIL'); $subject = 'Test Subject'; $content = 'Test Content'; - $from = 'sender@'.$domain; + $fromEmail = 'sender@'.$domain; $message = new Email( to: [$to], subject: $subject, content: $content, - from: $from, + fromName: 'Test Sender', + fromEmail: $fromEmail, ); $response = \json_decode($sender->send($message), true); diff --git a/tests/Messaging/Adapter/Email/SendgridTest.php b/tests/Messaging/Adapter/Email/SendgridTest.php index bcad2c38..9cef0cce 100644 --- a/tests/Messaging/Adapter/Email/SendgridTest.php +++ b/tests/Messaging/Adapter/Email/SendgridTest.php @@ -16,13 +16,14 @@ public function testSendEmail(): void $to = \getenv('TEST_EMAIL'); $subject = 'Test Subject'; $content = 'Test Content'; - $from = \getenv('TEST_FROM_EMAIL'); + $fromEmail = \getenv('TEST_FROM_EMAIL'); $message = new Email( to: [$to], subject: $subject, content: $content, - from: $from, + fromName: 'prateek', + fromEmail: $fromEmail, ); $response = \json_decode($sender->send($message), true);