Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ class Amazon_SES_Integration extends Integration {
/**
* Amazon SES API endpoint base URL.
*
* Includes the /v2/email/ prefix required by all SES v2 resource paths.
*
* @since 2.5.0
* @var string
*/
private const API_BASE = 'https://email.%s.amazonaws.com/v2/';
private const API_BASE = 'https://email.%s.amazonaws.com/v2/email/';

/**
* Constructor.
Expand Down Expand Up @@ -103,7 +105,7 @@ public function get_signer(): AWS_Signer {
*
* @since 2.5.0
*
* @param string $endpoint Relative endpoint path (e.g. 'email-identities').
* @param string $endpoint Relative endpoint path (e.g. 'identities', 'outbound-emails', 'account').
* @param string $method HTTP method. Defaults to GET.
* @param array $data Request body data (will be JSON-encoded for non-GET requests).
* @return array|\WP_Error Decoded response array or WP_Error on failure.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ public function on_domain_removed(string $domain, int $site_id): void {
}

$result = $this->get_ses()->ses_api_call(
'email-identities/' . rawurlencode($domain),
'identities/' . rawurlencode($domain),
'DELETE'
);

Expand All @@ -311,7 +311,7 @@ public function on_domain_removed(string $domain, int $site_id): void {
public function verify_domain(string $domain): array {

$result = $this->get_ses()->ses_api_call(
'email-identities',
'identities',
'POST',
[
'EmailIdentity' => $domain,
Expand Down Expand Up @@ -342,7 +342,7 @@ public function verify_domain(string $domain): array {
public function get_domain_verification_status(string $domain): array {

$result = $this->get_ses()->ses_api_call(
'email-identities/' . rawurlencode($domain)
'identities/' . rawurlencode($domain)
);

if (is_wp_error($result)) {
Expand All @@ -369,7 +369,7 @@ public function get_domain_verification_status(string $domain): array {
public function get_domain_dns_records(string $domain): array {

$result = $this->get_ses()->ses_api_call(
'email-identities/' . rawurlencode($domain)
'identities/' . rawurlencode($domain)
);

if (is_wp_error($result)) {
Expand Down Expand Up @@ -430,9 +430,11 @@ public function send_email(string $from, string $to, string $subject, string $bo
*/
public function get_sending_statistics(string $domain, string $period = '24h'): array {

// SES v2 does not expose per-domain stats directly via a simple endpoint.
// This returns account-level sending statistics as a proxy.
$result = $this->get_ses()->ses_api_call('account/sending-statistics');
// SES v2 exposes account-level quota via GET /v2/email/account.
// The SendQuota object returns the total sent in the last 24 hours.
// Per-domain or per-period bounce/complaint breakdown requires
// BatchGetMetricData; this returns the available quota-level summary.
$result = $this->get_ses()->ses_api_call('account');

if (is_wp_error($result)) {
return [
Expand All @@ -441,23 +443,15 @@ public function get_sending_statistics(string $domain, string $period = '24h'):
];
}

$stats = $result['SendingStatistics'] ?? [];
$quota = $result['SendQuota'] ?? [];

$totals = [
'sent' => 0,
'delivered' => 0,
return [
'success' => true,
'sent' => (int) ($quota['SentLast24Hours'] ?? 0),
'delivered' => (int) ($quota['SentLast24Hours'] ?? 0),
'bounced' => 0,
'complaints' => 0,
];

foreach ($stats as $stat) {
$totals['sent'] += (int) ($stat['DeliveryAttempts'] ?? 0);
$totals['bounced'] += (int) ($stat['Bounces'] ?? 0);
$totals['complaints'] += (int) ($stat['Complaints'] ?? 0);
$totals['delivered'] += (int) ($stat['DeliveryAttempts'] ?? 0) - (int) ($stat['Bounces'] ?? 0);
}

return array_merge(['success' => true], $totals);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public function test_get_api_base_includes_region(): void {

$this->assertStringContainsString('us-east-1', $api_base);
$this->assertStringContainsString('amazonaws.com', $api_base);
$this->assertStringContainsString('/v2/email/', $api_base);
}

public function test_get_api_base_uses_configured_region(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ public function test_verify_domain_returns_success_with_dns_records(): void {

$this->integration->expects($this->once())
->method('ses_api_call')
->with('email-identities', 'POST', $this->anything())
->with('identities', 'POST', $this->anything())
->willReturn([
'IdentityType' => 'DOMAIN',
'VerifiedForSendingStatus' => false,
Expand Down Expand Up @@ -171,7 +171,7 @@ public function test_get_domain_verification_status_returns_verified(): void {

$this->integration->expects($this->once())
->method('ses_api_call')
->with('email-identities/example.com')
->with('identities/example.com')
->willReturn([
'VerifiedForSendingStatus' => true,
'DkimAttributes' => ['Status' => 'SUCCESS'],
Expand Down Expand Up @@ -265,31 +265,29 @@ public function test_get_sending_statistics_returns_totals(): void {

$this->integration->expects($this->once())
->method('ses_api_call')
->with('account/sending-statistics')
->with('account')
->willReturn([
'SendingStatistics' => [
[
'DeliveryAttempts' => 100,
'Bounces' => 5,
'Complaints' => 2,
'Rejects' => 1,
],
'SendingEnabled' => true,
'SendQuota' => [
'Max24HourSend' => 50000,
'MaxSendRate' => 14,
'SentLast24Hours' => 100,
],
]);

$result = $this->module->get_sending_statistics('example.com');

$this->assertTrue($result['success']);
$this->assertSame(100, $result['sent']);
$this->assertSame(5, $result['bounced']);
$this->assertSame(2, $result['complaints']);
$this->assertSame(0, $result['bounced']);
$this->assertSame(0, $result['complaints']);
}

public function test_on_domain_added_calls_verify_domain(): void {

$this->integration->expects($this->once())
->method('ses_api_call')
->with('email-identities', 'POST', $this->anything())
->with('identities', 'POST', $this->anything())
->willReturn([
'DkimAttributes' => ['Tokens' => ['tok1', 'tok2', 'tok3']],
]);
Expand All @@ -311,7 +309,7 @@ public function test_on_domain_removed_deletes_when_filter_enabled(): void {

$this->integration->expects($this->once())
->method('ses_api_call')
->with('email-identities/example.com', 'DELETE')
->with('identities/example.com', 'DELETE')
->willReturn([]);

$this->module->on_domain_removed('example.com', 1);
Expand Down
Loading