Skip to content

Add Resend mailer adapter#440

Merged
armanist merged 2 commits intosoftberg:masterfrom
grigoryanmartin20:feature/#439-Add-Resend-mailer-adapter
Mar 27, 2026
Merged

Add Resend mailer adapter#440
armanist merged 2 commits intosoftberg:masterfrom
grigoryanmartin20:feature/#439-Add-Resend-mailer-adapter

Conversation

@grigoryanmartin20
Copy link
Copy Markdown
Collaborator

@grigoryanmartin20 grigoryanmartin20 commented Mar 27, 2026

Closes #439

Summary by CodeRabbit

  • New Features

    • Added support for Resend as an email service provider to send emails via Resend.
  • Chores

    • Added a test configuration entry to enable Resend API key in test settings.
  • Tests

    • Added unit tests covering Resend provider integration and factory/helper resolution.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

📝 Walkthrough

Walkthrough

Adds a new Resend mailer adapter: ResendAdapter plus MailerType::RESEND, factory registration, test config, and unit tests. The adapter prepares a Resend-format JSON payload and sends it via HTTP POST with Bearer auth to https://api.resend.com/emails.

Changes

Cohort / File(s) Summary
Core Adapter Implementation
src/Mailer/Adapters/ResendAdapter.php
New adapter class implementing MailerInterface and using MailerTrait. Stores api_key, prepares payload (from, to[], optional subject, html), and sends JSON POST to Resend with Authorization: Bearer <apiKey>.
Enums & Factory
src/Mailer/Enums/MailerType.php, src/Mailer/Factories/MailerFactory.php
Added MailerType::RESEND constant and registered ResendAdapter in MailerFactory::ADAPTERS.
Tests
tests/Unit/Mailer/Adapters/ResendAdapterTest.php, tests/Unit/Mailer/Factories/MailerFactoryTest.php, tests/Unit/Mailer/Helpers/MailerHelperFunctionsTest.php
Added tests for adapter instantiation, interface compliance, send() returning true, factory resolution, and helper function returning Mailer with Resend adapter.
Test Config
tests/_root/shared/config/mailer.php
Added resend config block with api_key entry ('resend_api_key').

Sequence Diagram

sequenceDiagram
    participant App as Application
    participant Adapter as ResendAdapter
    participant Client as HttpClient
    participant API as Resend API

    App->>Adapter: setFrom(), setAddress(), setSubject(), setBody()
    App->>Adapter: send()
    activate Adapter
    Adapter->>Adapter: prepare() -- build payload (from, to[], subject?, html)
    Adapter->>Client: post(url: "https://api.resend.com/emails", jsonPayload, headers: Authorization: Bearer ...)
    activate Client
    Client->>API: POST /emails
    API-->>Client: 200 OK (or error)
    Client-->>Adapter: response / exception
    deactivate Client
    Adapter-->>App: true / false
    deactivate Adapter
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I stitched a Resend thread tonight,

Payloads snug, headers tight,
From and to in tidy rows,
Hop—an HTTP POST goes,
Joyful mailings take to flight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add Resend mailer adapter' clearly and concisely summarizes the main change in the pull request.
Linked Issues check ✅ Passed All coding requirements from issue #439 are met: MailerType::RESEND constant added, ResendAdapter implemented with MailerInterface/MailerTrait, HttpClient-based implementation with Resend API integration, factory registration, test configuration, and unit tests for adapter and factory resolution.
Out of Scope Changes check ✅ Passed All changes in the pull request are directly related to implementing the Resend mailer adapter as specified in issue #439; no out-of-scope modifications were detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
src/Mailer/Factories/MailerFactory.php (1)

27-27: Minor: Import placement inconsistency.

The ResendAdapter import is placed after SmtpAdapter and before DiException, breaking the alphabetical grouping of adapter imports. Consider moving it to maintain consistency with other adapter imports.

Suggested reordering
 use Quantum\Mailer\Adapters\SendinblueAdapter;
 use Quantum\Mailer\Exceptions\MailerException;
 use Quantum\Config\Exceptions\ConfigException;
 use Quantum\Mailer\Adapters\MandrillAdapter;
+use Quantum\Mailer\Adapters\ResendAdapter;
 use Quantum\Mailer\Adapters\SendgridAdapter;
 use Quantum\Mailer\Adapters\MailgunAdapter;
 use Quantum\App\Exceptions\BaseException;
 use Quantum\Mailer\Adapters\SmtpAdapter;
-use Quantum\Mailer\Adapters\ResendAdapter;
 use Quantum\Di\Exceptions\DiException;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Mailer/Factories/MailerFactory.php` at line 27, The import for
ResendAdapter is out of alphabetical order among adapter imports; in
MailerFactory (class MailerFactory) move the line "use
Quantum\Mailer\Adapters\ResendAdapter;" so it sits with the other adapter
imports in correct alphabetical order (e.g., alongside SmtpAdapter,
SendmailAdapter, etc.) to restore consistent import grouping and keep unrelated
imports like DiException separate.
tests/Unit/Mailer/Adapters/ResendAdapterTest.php (1)

25-36: Test coverage note: sendEmail() is not exercised.

Since mail_trap is true in the test configuration, send() routes to saveEmail() rather than sendEmail(). This test validates the trait integration but does not cover the actual HTTP call logic in ResendAdapter::sendEmail().

This is consistent with other adapter tests in the codebase, but consider adding integration tests with mocked HTTP responses to ensure the Resend API payload and error handling are correct.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Unit/Mailer/Adapters/ResendAdapterTest.php` around lines 25 - 36, The
test only exercises the trait path because mail_trap=true so
ResendAdapter::send() calls saveEmail() and never exercises
ResendAdapter::sendEmail(); add a new unit/integration test that configures the
adapter to avoid mail trapping (or mocks the mail_trap check) and mocks the HTTP
client used by ResendAdapter to assert the POST payload and response handling of
sendEmail(), verifying successful responses and error paths (use the adapter's
sendEmail(), mock the HTTP client used inside ResendAdapter, and assert proper
request body, headers and exception handling).
src/Mailer/Adapters/ResendAdapter.php (2)

94-94: Potential issue: json_encode can return false.

json_encode($this->data) can return false on encoding failure (e.g., malformed UTF-8). Passing false to setData() may cause unexpected behavior.

Suggested fix with error handling
-                ->setData(json_encode($this->data))
+                ->setData(json_encode($this->data, JSON_THROW_ON_ERROR))

This will throw a JsonException on encoding failure, which will be caught by the existing catch (Exception $e) block.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Mailer/Adapters/ResendAdapter.php` at line 94, Replace the raw
json_encode call used in the ResendAdapter when setting payload data (the
->setData(json_encode($this->data)) call) with a call that fails loudly on
encoding errors: either use json_encode($this->data, JSON_THROW_ON_ERROR) so a
JsonException is thrown into the existing catch (Exception $e) block, or
explicitly check for a false return and throw a JsonException before calling
setData; ensure you reference the same setData() invocation and $this->data so
encoding failures aren’t passed silently.

35-37: Use native type declaration for $apiKey.

The property uses a docblock @var string|null but lacks a native PHP type declaration. For consistency with PHP 7.4+ style used elsewhere in the codebase, use the native type.

Suggested fix
-    /**
-     * `@var` string|null
-     */
-    private $apiKey;
+    private ?string $apiKey;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Mailer/Adapters/ResendAdapter.php` around lines 35 - 37, The $apiKey
property in the ResendAdapter lacks a native type declaration; change its
declaration to use the nullable string type (e.g., declare the property as
?string $apiKey) so it matches the `@var` string|null docblock and project's PHP
7.4+ style; update any constructor parameter or assignments in ResendAdapter
that set $apiKey to ensure types remain compatible with the new ?string
property.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Mailer/Adapters/ResendAdapter.php`:
- Around line 61-65: Remove the trailing whitespace (or tabs) on the line that
assigns $fromName and ensure indentation uses spaces consistently; specifically
update the block that sets $fromName and $this->data['from'] (the lines
referencing $fromName and $this->data['from'] in ResendAdapter) to eliminate any
trailing spaces/tabs so PHP CS Fixer no longer flags the file.
- Around line 84-101: sendEmail() currently returns true immediately after
$this->httpClient->start() without checking the HTTP response; change it to call
$this->httpClient->info(CURLINFO_HTTP_CODE) after start(), treat only 2xx codes
as success, and return false (and/or log the error) for 4xx/5xx statuses; update
the same pattern in other adapters (SendgridAdapter, MailgunAdapter,
MandrillAdapter) so each method uses the HTTP code from info(CURLINFO_HTTP_CODE)
to decide success/failure instead of assuming start() succeeded.

In `@tests/Unit/Mailer/Adapters/ResendAdapterTest.php`:
- Around line 37-38: Remove the trailing blank line at the end of the file to
satisfy PHP CS Fixer: open the tests/Unit/Mailer/Adapters/ResendAdapterTest.php
file (containing the ResendAdapterTest class) and delete the extra empty newline
after the closing brace so the file ends immediately after the final "}"
character with a single newline or no extra blank line.

---

Nitpick comments:
In `@src/Mailer/Adapters/ResendAdapter.php`:
- Line 94: Replace the raw json_encode call used in the ResendAdapter when
setting payload data (the ->setData(json_encode($this->data)) call) with a call
that fails loudly on encoding errors: either use json_encode($this->data,
JSON_THROW_ON_ERROR) so a JsonException is thrown into the existing catch
(Exception $e) block, or explicitly check for a false return and throw a
JsonException before calling setData; ensure you reference the same setData()
invocation and $this->data so encoding failures aren’t passed silently.
- Around line 35-37: The $apiKey property in the ResendAdapter lacks a native
type declaration; change its declaration to use the nullable string type (e.g.,
declare the property as ?string $apiKey) so it matches the `@var` string|null
docblock and project's PHP 7.4+ style; update any constructor parameter or
assignments in ResendAdapter that set $apiKey to ensure types remain compatible
with the new ?string property.

In `@src/Mailer/Factories/MailerFactory.php`:
- Line 27: The import for ResendAdapter is out of alphabetical order among
adapter imports; in MailerFactory (class MailerFactory) move the line "use
Quantum\Mailer\Adapters\ResendAdapter;" so it sits with the other adapter
imports in correct alphabetical order (e.g., alongside SmtpAdapter,
SendmailAdapter, etc.) to restore consistent import grouping and keep unrelated
imports like DiException separate.

In `@tests/Unit/Mailer/Adapters/ResendAdapterTest.php`:
- Around line 25-36: The test only exercises the trait path because
mail_trap=true so ResendAdapter::send() calls saveEmail() and never exercises
ResendAdapter::sendEmail(); add a new unit/integration test that configures the
adapter to avoid mail trapping (or mocks the mail_trap check) and mocks the HTTP
client used by ResendAdapter to assert the POST payload and response handling of
sendEmail(), verifying successful responses and error paths (use the adapter's
sendEmail(), mock the HTTP client used inside ResendAdapter, and assert proper
request body, headers and exception handling).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 580c86e6-bf1d-4f64-abd8-d80b38191815

📥 Commits

Reviewing files that changed from the base of the PR and between 404fcb5 and ea38c5d.

📒 Files selected for processing (7)
  • src/Mailer/Adapters/ResendAdapter.php
  • src/Mailer/Enums/MailerType.php
  • src/Mailer/Factories/MailerFactory.php
  • tests/Unit/Mailer/Adapters/ResendAdapterTest.php
  • tests/Unit/Mailer/Factories/MailerFactoryTest.php
  • tests/Unit/Mailer/Helpers/MailerHelperFunctionsTest.php
  • tests/_root/shared/config/mailer.php

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 27, 2026

Codecov Report

❌ Patch coverage is 86.20690% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.65%. Comparing base (404fcb5) to head (ee70e77).
⚠️ Report is 3 commits behind head on master.

Files with missing lines Patch % Lines
src/Mailer/Adapters/ResendAdapter.php 86.20% 4 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master     #440      +/-   ##
============================================
+ Coverage     82.64%   82.65%   +0.01%     
- Complexity     2721     2730       +9     
============================================
  Files           241      242       +1     
  Lines          7305     7334      +29     
============================================
+ Hits           6037     6062      +25     
- Misses         1268     1272       +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/Mailer/Adapters/ResendAdapter.php (1)

50-54: Consider adding defensive validation for api_key parameter.

If api_key is missing from $params, this will produce an undefined array key warning. Consider using null coalescing with a fallback or explicit validation.

♻️ Suggested defensive approach
     public function __construct(array $params)
     {
         $this->httpClient = new HttpClient();
-        $this->apiKey = $params['api_key'];
+        $this->apiKey = $params['api_key'] ?? null;
     }

Alternatively, throw an explicit exception if the key is required:

if (!isset($params['api_key'])) {
    throw new \InvalidArgumentException('Resend adapter requires api_key parameter');
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Mailer/Adapters/ResendAdapter.php` around lines 50 - 54, The constructor
(__construct) currently assigns $this->apiKey = $params['api_key'] which can
raise an undefined index warning; add defensive validation in the ResendAdapter
constructor to either (a) check isset($params['api_key']) and throw an
\InvalidArgumentException with a clear message if missing, or (b) use the
null-coalescing fallback ($params['api_key'] ?? null) and handle a null apiKey
later; ensure the chosen approach sets $this->apiKey deterministically and
documents the requirement in the constructor.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/Mailer/Adapters/ResendAdapter.php`:
- Around line 50-54: The constructor (__construct) currently assigns
$this->apiKey = $params['api_key'] which can raise an undefined index warning;
add defensive validation in the ResendAdapter constructor to either (a) check
isset($params['api_key']) and throw an \InvalidArgumentException with a clear
message if missing, or (b) use the null-coalescing fallback ($params['api_key']
?? null) and handle a null apiKey later; ensure the chosen approach sets
$this->apiKey deterministically and documents the requirement in the
constructor.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c9e7d976-1d23-4f67-90f8-e41013752268

📥 Commits

Reviewing files that changed from the base of the PR and between ea38c5d and ee70e77.

📒 Files selected for processing (2)
  • src/Mailer/Adapters/ResendAdapter.php
  • tests/Unit/Mailer/Adapters/ResendAdapterTest.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/Unit/Mailer/Adapters/ResendAdapterTest.php

@armanist armanist merged commit 9cd6ee5 into softberg:master Mar 27, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Resend mailer adapter

2 participants