Skip to content

Commit 6f6caaa

Browse files
committed
feat: add round robin and ses transports
1 parent 8d2eb89 commit 6f6caaa

File tree

8 files changed

+249
-2
lines changed

8 files changed

+249
-2
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
"rector/rector": "^2.0-rc2",
6565
"spatie/phpunit-snapshot-assertions": "^5.1.8",
6666
"spaze/phpstan-disallowed-calls": "^4.0",
67+
"symfony/amazon-mailer": "^7.2.0",
6768
"symfony/postmark-mailer": "^7.2.6",
6869
"symplify/monorepo-builder": "^11.2",
6970
"tempest/blade": "^0.1.0",

packages/mailer/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"symfony/mailer": "^7.2.6"
1111
},
1212
"require-dev": {
13-
"symfony/postmark-mailer": "^7.2.6"
13+
"symfony/postmark-mailer": "^7.2.6",
14+
"symfony/amazon-mailer": "^7.2.0"
1415
},
1516
"autoload": {
1617
"psr-4": {

packages/mailer/src/Exceptions/MissingTransportException.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
namespace Tempest\Mail\Exceptions;
44

55
use Exception;
6+
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesApiAsyncAwsTransport;
7+
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesHttpAsyncAwsTransport;
8+
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesSmtpTransport;
69
use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkApiTransport;
710
use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkSmtpTransport;
811

@@ -24,6 +27,9 @@ private function getPackageName(): ?string
2427
return match ($this->missing) {
2528
PostmarkApiTransport::class => 'symfony/postmark-mailer',
2629
PostmarkSmtpTransport::class => 'symfony/postmark-mailer',
30+
SesApiAsyncAwsTransport::class => 'symfony/amazon-mailer',
31+
SesHttpAsyncAwsTransport::class => 'symfony/amazon-mailer',
32+
SesSmtpTransport::class => 'symfony/amazon-mailer',
2733
default => null,
2834
};
2935
}

packages/mailer/src/GenericMailer.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use Symfony\Component\Mailer\Transport\TransportInterface;
66
use Tempest\Mail\Exceptions\MissingTransportException;
77
use Tempest\Mail\MailerConfig;
8-
use Tempest\View\View;
98
use Tempest\View\ViewRenderer;
109

1110
use function Tempest\map;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
namespace Tempest\Mail\Transports;
4+
5+
use Symfony\Component\Mailer\Transport\RoundRobinTransport;
6+
use Symfony\Component\Mailer\Transport\TransportInterface;
7+
use Tempest\DateTime\Duration;
8+
use Tempest\Mail\Address;
9+
use Tempest\Mail\MailerConfig;
10+
use Tempest\Support\Arr;
11+
use UnitEnum;
12+
13+
/**
14+
* Send emails using the round-robin balancing strategy.
15+
*/
16+
final class RoundRobinMailerConfig implements MailerConfig
17+
{
18+
public string $transport = RoundRobinTransport::class;
19+
20+
public function __construct(
21+
/**
22+
* List of configurations to use.
23+
*
24+
* @var MailerConfig[]
25+
*/
26+
public array $transports,
27+
28+
/**
29+
* The period in seconds to wait before retrying a failed transport.
30+
*/
31+
public int|Duration $waitTimeBeforeRetrying = 60,
32+
33+
/**
34+
* @deprecated Currently ignored.
35+
*/
36+
public null|string|Address $from = null,
37+
38+
/*
39+
* Identifies the {@see \Tempest\Mail\Mailer} instance in the container, in case you need more than one configuration.
40+
*/
41+
public null|string|UnitEnum $tag = null,
42+
) {}
43+
44+
public function createTransport(): TransportInterface
45+
{
46+
return new RoundRobinTransport(
47+
transports: $this->buildTransports(),
48+
retryPeriod: ($this->waitTimeBeforeRetrying instanceof Duration)
49+
? $this->waitTimeBeforeRetrying->getTotalSeconds()
50+
: $this->waitTimeBeforeRetrying,
51+
);
52+
}
53+
54+
/** @return TransportInterface[] */
55+
private function buildTransports(): array
56+
{
57+
return Arr\map_iterable($this->transports, fn (MailerConfig $config) => $config->createTransport());
58+
}
59+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Tempest\Mail\Transports\Ses;
4+
5+
enum Scheme: string
6+
{
7+
/**
8+
* Uses Amazon SES's API.
9+
*
10+
* @see https://docs.aws.amazon.com/ses/latest/dg/send-email-api.html
11+
*/
12+
case API = 'ses+api';
13+
14+
/**
15+
* Uses Amazon SES's async HTTP transport.
16+
*
17+
* @see https://docs.aws.amazon.com/ses/latest/dg/send-email-api.html
18+
*/
19+
case HTTP = 'ses+https';
20+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
namespace Tempest\Mail\Transports\Ses;
4+
5+
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesApiAsyncAwsTransport;
6+
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesHttpAsyncAwsTransport;
7+
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesSmtpTransport;
8+
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory;
9+
use Symfony\Component\Mailer\Transport\Dsn;
10+
use Symfony\Component\Mailer\Transport\NullTransport;
11+
use Symfony\Component\Mailer\Transport\TransportInterface;
12+
use Tempest\Mail\Address;
13+
use Tempest\Mail\MailerConfig;
14+
use UnitEnum;
15+
16+
/**
17+
* Send emails using Amazon SES.
18+
*/
19+
final class SesMailerConfig implements MailerConfig
20+
{
21+
public string $transport {
22+
get => match ($this->sceme) {
23+
Scheme::API => SesApiAsyncAwsTransport::class,
24+
Scheme::HTTP => SesHttpAsyncAwsTransport::class,
25+
};
26+
}
27+
28+
public function __construct(
29+
/**
30+
* Access key used for authenticating to the SES API.
31+
*/
32+
public string $accessKey,
33+
34+
/**
35+
* Secret key used for authenticating to the SES API.
36+
*/
37+
public string $secretKey,
38+
39+
/**
40+
* Region configured in your SES account.
41+
*/
42+
public string $region,
43+
44+
/**
45+
* An optional endpoint to use instead of the default one.
46+
*/
47+
public ?string $host = null,
48+
49+
/**
50+
* An optional Amazon SES session token.
51+
*/
52+
public ?string $sessionToken = null,
53+
54+
/**
55+
* Address from which emails are sent by default.
56+
*/
57+
public null|string|Address $from = null,
58+
59+
/**
60+
* Whether to use Amazon SES's API or async HTTP transport.
61+
*/
62+
public Scheme $scheme = Scheme::HTTP,
63+
64+
/*
65+
* Identifies the {@see \Tempest\Mail\Mailer} instance in the container, in case you need more than one configuration.
66+
*/
67+
public null|string|UnitEnum $tag = null,
68+
) {}
69+
70+
public function createTransport(): TransportInterface
71+
{
72+
return new SesTransportFactory()->create(new Dsn(
73+
scheme: $this->scheme->value,
74+
user: $this->accessKey,
75+
password: $this->secretKey,
76+
host: $this->host ?? 'default',
77+
options: [
78+
'region' => $this->region,
79+
'session_token' => $this->sessionToken,
80+
],
81+
));
82+
}
83+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
namespace Tempest\Mail\Transports\Ses;
4+
5+
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesApiAsyncAwsTransport;
6+
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesHttpAsyncAwsTransport;
7+
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesSmtpTransport;
8+
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesTransportFactory;
9+
use Symfony\Component\Mailer\Transport\Dsn;
10+
use Symfony\Component\Mailer\Transport\NullTransport;
11+
use Symfony\Component\Mailer\Transport\TransportInterface;
12+
use Tempest\Mail\Address;
13+
use Tempest\Mail\MailerConfig;
14+
use UnitEnum;
15+
16+
/**
17+
* Send emails using Amazon SES.
18+
*/
19+
final class SesSmtpMailerConfig implements MailerConfig
20+
{
21+
public string $transport = SesSmtpTransport::class;
22+
23+
public function __construct(
24+
/**
25+
* Access key used for authenticating to the SES API.
26+
*/
27+
public string $username,
28+
29+
/**
30+
* Secret key used for authenticating to the SES API.
31+
*/
32+
public string $password,
33+
34+
/**
35+
* Region configured in your SES account.
36+
*/
37+
public string $region,
38+
39+
/**
40+
* An optional endpoint to use instead of the default one.
41+
*/
42+
public ?string $host = null,
43+
44+
/**
45+
* The minimum number of seconds between two messages required to ping the server.
46+
*/
47+
public ?int $pingThreshold = null,
48+
49+
/**
50+
* Address from which emails are sent by default.
51+
*/
52+
public null|string|Address $from = null,
53+
54+
/**
55+
* Whether to use Amazon SES's API or SMTP server.
56+
*/
57+
public Scheme $scheme = Scheme::API,
58+
59+
/*
60+
* Identifies the {@see \Tempest\Mail\Mailer} instance in the container, in case you need more than one configuration.
61+
*/
62+
public null|string|UnitEnum $tag = null,
63+
) {}
64+
65+
public function createTransport(): TransportInterface
66+
{
67+
return new SesTransportFactory()->create(new Dsn(
68+
scheme: 'ses+smtp',
69+
user: $this->username,
70+
password: $this->password,
71+
host: $this->host ?? 'default',
72+
options: [
73+
'ping_threshold' => $this->pingThreshold,
74+
'region' => $this->region,
75+
],
76+
));
77+
}
78+
}

0 commit comments

Comments
 (0)