-
Notifications
You must be signed in to change notification settings - Fork 68
Add SparkPost email adapter #146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| <?php | ||
|
|
||
| namespace Utopia\Messaging\Adapter\Email; | ||
|
|
||
| use Utopia\Messaging\Adapter\Email as EmailAdapter; | ||
| use Utopia\Messaging\Messages\Email; | ||
| use Utopia\Messaging\Response; | ||
|
|
||
| class SparkPost extends EmailAdapter | ||
| { | ||
| public function __construct( | ||
| private string $apiKey, | ||
| private bool $isEu = false | ||
| ) { | ||
| parent::__construct(); | ||
| } | ||
|
|
||
| public function getName(): string | ||
| { | ||
| return 'SparkPost'; | ||
| } | ||
|
|
||
| public function getMaxMessagesPerRequest(): int | ||
| { | ||
| return 1000; | ||
| } | ||
|
|
||
| protected function process(Email $message): array | ||
| { | ||
| $usDomain = 'api.sparkpost.com'; | ||
| $euDomain = 'api.eu.sparkpost.com'; | ||
|
|
||
| $domain = $this->isEu ? $euDomain : $usDomain; | ||
|
|
||
| $response = new Response($this->getType()); | ||
| $result = $this->request( | ||
| method: 'POST', | ||
| url: "https://$domain/api/v1/transmissions", | ||
| headers: [ | ||
| 'Authorization: ' . $this->apiKey, | ||
| 'Content-Type: application/json', | ||
| ], | ||
| body: [ | ||
| 'options' => [ | ||
| 'sandbox' => false, | ||
| ], | ||
| 'recipients' => [ | ||
| [ | ||
| 'address' => [ | ||
| 'email' => $message->getTo()[0], | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an email has 2–1000 Prompt To Fix With AIThis is a comment left during a code review.
Path: src/Utopia/Messaging/Adapter/Email/SparkPost.php
Line: 50
Comment:
**Additional recipients are silently dropped**
When an email has 2–1000 `to` recipients, the base adapter accepts it and invokes `process()` once, but this payload includes only `getTo()[0]`. Every remaining recipient is omitted from both delivery and response results.
How can I resolve this? If you propose a fix, please make it concise. |
||
| ], | ||
| ], | ||
|
Comment on lines
+47
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an email contains CC or BCC recipients, the transmission body includes only the first Prompt To Fix With AIThis is a comment left during a code review.
Path: src/Utopia/Messaging/Adapter/Email/SparkPost.php
Line: 47-52
Comment:
**CC and BCC recipients are omitted**
When an email contains CC or BCC recipients, the transmission body includes only the first `to` recipient and never adds either recipient group, causing those recipients to be silently excluded from delivery.
How can I resolve this? If you propose a fix, please make it concise. |
||
| ], | ||
| 'content' => [ | ||
| 'from' => [ | ||
| 'email' => $message->getFrom(), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When any valid email is sent, this calls Prompt To Fix With AIThis is a comment left during a code review.
Path: src/Utopia/Messaging/Adapter/Email/SparkPost.php
Line: 56
Comment:
**Undefined sender accessor crashes sends**
When any valid email is sent, this calls `Email::getFrom()`, but the message exposes only `getFromEmail()` and `getFromName()`, causing an undefined-method error before the SparkPost request is made.
How can I resolve this? If you propose a fix, please make it concise. |
||
| ], | ||
| 'subject' => $message->getSubject(), | ||
| 'html' => $message->isHtml() ? $message->getContent() : null, | ||
| 'text' => $message->isHtml() ? null : $message->getContent(), | ||
| ], | ||
| ], | ||
| ); | ||
|
|
||
| if ($result['statusCode'] >= 200 && $result['statusCode'] < 300) { | ||
| $response->addResult($message->getTo()[0]); | ||
|
Comment on lines
+65
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When SparkPost accepts a transmission, this branch adds a successful result but never calls Prompt To Fix With AIThis is a comment left during a code review.
Path: src/Utopia/Messaging/Adapter/Email/SparkPost.php
Line: 65-66
Comment:
**Successful delivery count remains zero**
When SparkPost accepts a transmission, this branch adds a successful result but never calls `setDeliveredTo()`. The returned response therefore reports zero deliveries even though the email was sent, breaking delivery accounting and response assertions.
How can I resolve this? If you propose a fix, please make it concise. |
||
| } else { | ||
| $error = $result['response']['errors'][0]['message'] ?? $result['error'] ?? 'Unknown error'; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When SparkPost or an intermediary returns a plain-text or HTML error body, the request helper preserves Prompt To Fix With AIThis is a comment left during a code review.
Path: src/Utopia/Messaging/Adapter/Email/SparkPost.php
Line: 68
Comment:
**Non-JSON errors break response handling**
When SparkPost or an intermediary returns a plain-text or HTML error body, the request helper preserves `response` as a string, but this line accesses it as a nested array. Error handling then raises an offset error instead of returning the provider's failure response.
How can I resolve this? If you propose a fix, please make it concise. |
||
| $response->addResult($message->getTo()[0], $error); | ||
| } | ||
|
|
||
| return $response->toArray(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| <?php | ||
|
|
||
| namespace Utopia\Tests\Adapter\Email; | ||
|
|
||
| use Utopia\Messaging\Adapter\Email\SparkPost; | ||
| use Utopia\Messaging\Messages\Email; | ||
| use Utopia\Tests\Adapter\Base; | ||
|
|
||
| class SparkPostTest extends Base | ||
| { | ||
| public function testSendEmail(): void | ||
| { | ||
| $sender = new SparkPost(\getenv('SPARKPOST_API_KEY')); | ||
|
|
||
| $message = new Email( | ||
| to: [\getenv('TEST_EMAIL')], | ||
| subject: 'Test Subject', | ||
| content: 'Test Content', | ||
| ); | ||
|
Comment on lines
+15
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Running this test constructs Prompt To Fix With AIThis is a comment left during a code review.
Path: tests/e2e/Email/SparkPostTest.php
Line: 15-19
Comment:
**Required sender arguments are omitted**
Running this test constructs `Email` without its required `fromName` and `fromEmail` arguments, causing an argument-count error before the adapter is invoked.
How can I resolve this? If you propose a fix, please make it concise. |
||
|
|
||
| $response = $sender->send($message); | ||
|
|
||
| $this->assertResponse($response); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getTo()[0]is a normalized recipient array, not an address string. This places an object in SparkPost'saddress.emailfield, and the same value passed toResponse::addResult()causes a type error because that method requires a string.Prompt To Fix With AI