Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions src/Utopia/Messaging/Adapter/Email/SparkPost.php
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],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Recipient records used as strings

getTo()[0] is a normalized recipient array, not an address string. This places an object in SparkPost's address.email field, and the same value passed to Response::addResult() causes a type error because that method requires a string.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/Utopia/Messaging/Adapter/Email/SparkPost.php
Line: 50

Comment:
**Recipient records used as strings**

`getTo()[0]` is a normalized recipient array, not an address string. This places an object in SparkPost's `address.email` field, and the same value passed to `Response::addResult()` causes a type error because that method requires a string.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Codex

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Prompt To Fix With AI
This 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.

Fix in Claude Code Fix in Codex

],
],
Comment on lines +47 to +52

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Prompt To Fix With AI
This 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.

Fix in Claude Code Fix in Codex

],
'content' => [
'from' => [
'email' => $message->getFrom(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Prompt To Fix With AI
This 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.

Fix in Claude Code Fix in Codex

],
'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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Prompt To Fix With AI
This 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.

Fix in Claude Code Fix in Codex

} else {
$error = $result['response']['errors'][0]['message'] ?? $result['error'] ?? 'Unknown error';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Prompt To Fix With AI
This 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.

Fix in Claude Code Fix in Codex

$response->addResult($message->getTo()[0], $error);
}

return $response->toArray();
}
}
25 changes: 25 additions & 0 deletions tests/e2e/Email/SparkPostTest.php
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Prompt To Fix With AI
This 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.

Fix in Claude Code Fix in Codex


$response = $sender->send($message);

$this->assertResponse($response);
}
}