├── .gitattributes ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json └── src ├── Enum ├── AttachmentContentDisposition.php ├── AttachmentContentTransferEncoding.php └── SuppressionListReason.php ├── Exception ├── AccountSuspendedException.php ├── BadRequestException.php ├── LimitExceededException.php ├── MailFromDomainNotVerifiedException.php ├── MessageRejectedException.php ├── NotFoundException.php ├── SendingPausedException.php └── TooManyRequestsException.php ├── Input ├── DeleteSuppressedDestinationRequest.php ├── GetSuppressedDestinationRequest.php └── SendEmailRequest.php ├── Result ├── DeleteSuppressedDestinationResponse.php ├── GetSuppressedDestinationResponse.php └── SendEmailResponse.php ├── SesClient.php └── ValueObject ├── Attachment.php ├── Body.php ├── Content.php ├── Destination.php ├── EmailContent.php ├── EmailTemplateContent.php ├── ListManagementOptions.php ├── Message.php ├── MessageHeader.php ├── MessageTag.php ├── RawMessage.php ├── SuppressedDestination.php ├── SuppressedDestinationAttributes.php └── Template.php /.gitattributes: -------------------------------------------------------------------------------- 1 | /.github export-ignore 2 | /tests export-ignore 3 | /.gitignore export-ignore 4 | /Makefile export-ignore 5 | /phpunit.xml.dist export-ignore 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## NOT RELEASED 4 | 5 | ### Changed 6 | 7 | - AWS enhancement: Documentation updates. 8 | 9 | ## 1.12.0 10 | 11 | ### Added 12 | 13 | - AWS api-change: This release enables customers to provide attachments in the SESv2 SendEmail and SendBulkEmail APIs. 14 | 15 | ### Changed 16 | 17 | - Sort exception alphabetically. 18 | 19 | ## 1.11.0 20 | 21 | ### Added 22 | 23 | - Added support for `getSuppressedDestination` and `deleteSuppressedDestination` to manage SESv2 suppression list entries. 24 | 25 | ## 1.10.0 26 | 27 | ### Added 28 | 29 | - AWS api-change: Introduces support for multi-region endpoint. 30 | 31 | ## 1.9.0 32 | 33 | ### Added 34 | 35 | - AWS api-change: This release enables customers to provide the email template content in the SESv2 SendEmail and SendBulkEmail APIs instead of the name or the ARN of a stored email template. 36 | 37 | ## 1.8.2 38 | 39 | ### Changed 40 | 41 | - Enable compiler optimization for the `sprintf` function. 42 | 43 | ## 1.8.1 44 | 45 | ### Changed 46 | 47 | - Add `Accept: application/json` header in request to fix incompatibility with 3rd party providers 48 | 49 | ## 1.8.0 50 | 51 | ### Added 52 | 53 | - AWS api-change: Adds support for providing custom headers within SendEmail and SendBulkEmail for SESv2. 54 | - AWS api-change: Added `fips-us-gov-east-1` region 55 | 56 | ### Changed 57 | 58 | - AWS enhancement: Documentation updates. 59 | 60 | ## 1.7.0 61 | 62 | ### Added 63 | 64 | - AWS api-change: Added `fips-ca-central-1`, `fips-us-east-2` and `fips-us-west-1` regions 65 | 66 | ### Changed 67 | 68 | - Allow passing explicit null values for optional fields of input objects 69 | - AWS enhancement: Documentation updates. 70 | 71 | ## 1.6.0 72 | 73 | ### Added 74 | 75 | - Avoid overriding the exception message with the raw message 76 | 77 | ### Changed 78 | 79 | - Improve parameter type and return type in phpdoc 80 | - Avoid overriding the exception message with the raw message 81 | 82 | ## 1.5.0 83 | 84 | ### Added 85 | 86 | - AWS api-change: reorder regions 87 | - AWS api-change: Added `fips-us-east-1` and `fips-us-west-2` regions 88 | 89 | ## 1.4.1 90 | 91 | ### Fixed 92 | 93 | - Assert the provided Input can be json-encoded. 94 | - AWS api-change: This release includes the ability to use 2048 bits RSA key pairs for DKIM in SES, either with Easy DKIM or Bring Your Own DKIM. 95 | 96 | ## 1.4.0 97 | 98 | ### Added 99 | 100 | - Changed case of object's properties to camelCase. 101 | - Added documentation in class headers. 102 | - Added domain exceptions 103 | 104 | ## 1.3.1 105 | 106 | ### Fixed 107 | 108 | - Fallback to default region config if provided region is not defined 109 | 110 | ## 1.3.0 111 | 112 | ### Added 113 | 114 | - Enables customers to manage their own contact lists and end-user subscription preferences 115 | 116 | ## 1.2.0 117 | 118 | ### Added 119 | 120 | - Support for PHP 8. 121 | - Support for `FromEmailAddressIdentityArn` and `FeedbackForwardingEmailAddressIdentityArn` in `SendEmailRequest`. 122 | 123 | ## 1.1.1 124 | 125 | ### Fixed 126 | 127 | - Fix invalid signature in SES client because of wrong Scoped Service. 128 | 129 | ## 1.1.0 130 | 131 | ### Deprecated 132 | 133 | - Protected methods `getServiceCode`, `getSignatureVersion` and `getSignatureScopeName` of `SesClient` are deprecated and will be removed in 2.0 134 | 135 | ## 1.0.0 136 | 137 | ### Added 138 | 139 | - Support for async-aws/core 1.0. 140 | 141 | ## 0.4.0 142 | 143 | ### Removed 144 | 145 | - Dependency on `symfony/http-client-contracts` 146 | - All `validate()` methods on the inputs. They are merged with `request()`. 147 | 148 | ### Changed 149 | 150 | - Moved value objects to a dedicated namespace. 151 | - Results' `populateResult()` has only one argument. It takes a `AsyncAws\Core\Response`. 152 | - The `AsyncAws\Ses\Input\*` and `AsyncAws\Ses\ValueObject*` classes are marked final. 153 | 154 | ## 0.3.0 155 | 156 | ### Changed 157 | 158 | - Removed `requestBody()`, `requestHeaders()`, `requestQuery()` and `requestUri()` input classes. They are replaced with `request()`. 159 | - Using async-aws/core: 0.4.0 160 | 161 | ### Fixed 162 | 163 | - `Action` and `Version` do not need to be part of every body. 164 | 165 | ## 0.2.0 166 | 167 | ### Changed 168 | 169 | - Using async-aws/core: 0.3.0 170 | 171 | ## 0.1.0 172 | 173 | First version 174 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Jérémy Derussé, Tobias Nyholm 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AsyncAws SES Client 2 | 3 | ![](https://github.com/async-aws/ses/workflows/Tests/badge.svg?branch=master) 4 | ![](https://github.com/async-aws/ses/workflows/BC%20Check/badge.svg?branch=master) 5 | 6 | An API client for SES. 7 | 8 | ## Install 9 | 10 | ```cli 11 | composer require async-aws/ses 12 | ``` 13 | 14 | ## Documentation 15 | 16 | See https://async-aws.com/clients/ses.html for documentation. 17 | 18 | ## Contribute 19 | 20 | Contributions are welcome and appreciated. Please read https://async-aws.com/contribute/ 21 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-aws/ses", 3 | "description": "SES client, part of the AWS SDK provided by AsyncAws.", 4 | "license": "MIT", 5 | "type": "library", 6 | "keywords": [ 7 | "aws", 8 | "amazon", 9 | "sdk", 10 | "async-aws", 11 | "ses" 12 | ], 13 | "require": { 14 | "php": "^7.2.5 || ^8.0", 15 | "ext-json": "*", 16 | "async-aws/core": "^1.9" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "AsyncAws\\Ses\\": "src" 21 | } 22 | }, 23 | "autoload-dev": { 24 | "psr-4": { 25 | "AsyncAws\\Ses\\Tests\\": "tests/" 26 | } 27 | }, 28 | "extra": { 29 | "branch-alias": { 30 | "dev-master": "1.12-dev" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Enum/AttachmentContentDisposition.php: -------------------------------------------------------------------------------- 1 | true, 14 | self::INLINE => true, 15 | ][$value]); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Enum/AttachmentContentTransferEncoding.php: -------------------------------------------------------------------------------- 1 | true, 15 | self::QUOTED_PRINTABLE => true, 16 | self::SEVEN_BIT => true, 17 | ][$value]); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Enum/SuppressionListReason.php: -------------------------------------------------------------------------------- 1 | true, 23 | self::COMPLAINT => true, 24 | ][$value]); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Exception/AccountSuspendedException.php: -------------------------------------------------------------------------------- 1 | emailAddress = $input['EmailAddress'] ?? null; 33 | parent::__construct($input); 34 | } 35 | 36 | /** 37 | * @param array{ 38 | * EmailAddress?: string, 39 | * '@region'?: string|null, 40 | * }|DeleteSuppressedDestinationRequest $input 41 | */ 42 | public static function create($input): self 43 | { 44 | return $input instanceof self ? $input : new self($input); 45 | } 46 | 47 | public function getEmailAddress(): ?string 48 | { 49 | return $this->emailAddress; 50 | } 51 | 52 | /** 53 | * @internal 54 | */ 55 | public function request(): Request 56 | { 57 | // Prepare headers 58 | $headers = [ 59 | 'Content-Type' => 'application/json', 60 | 'Accept' => 'application/json', 61 | ]; 62 | 63 | // Prepare query 64 | $query = []; 65 | 66 | // Prepare URI 67 | $uri = []; 68 | if (null === $v = $this->emailAddress) { 69 | throw new InvalidArgument(\sprintf('Missing parameter "EmailAddress" for "%s". The value cannot be null.', __CLASS__)); 70 | } 71 | $uri['EmailAddress'] = $v; 72 | $uriString = '/v2/email/suppression/addresses/' . rawurlencode($uri['EmailAddress']); 73 | 74 | // Prepare Body 75 | $body = ''; 76 | 77 | // Return the Request 78 | return new Request('DELETE', $uriString, $query, $headers, StreamFactory::create($body)); 79 | } 80 | 81 | public function setEmailAddress(?string $value): self 82 | { 83 | $this->emailAddress = $value; 84 | 85 | return $this; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Input/GetSuppressedDestinationRequest.php: -------------------------------------------------------------------------------- 1 | emailAddress = $input['EmailAddress'] ?? null; 33 | parent::__construct($input); 34 | } 35 | 36 | /** 37 | * @param array{ 38 | * EmailAddress?: string, 39 | * '@region'?: string|null, 40 | * }|GetSuppressedDestinationRequest $input 41 | */ 42 | public static function create($input): self 43 | { 44 | return $input instanceof self ? $input : new self($input); 45 | } 46 | 47 | public function getEmailAddress(): ?string 48 | { 49 | return $this->emailAddress; 50 | } 51 | 52 | /** 53 | * @internal 54 | */ 55 | public function request(): Request 56 | { 57 | // Prepare headers 58 | $headers = [ 59 | 'Content-Type' => 'application/json', 60 | 'Accept' => 'application/json', 61 | ]; 62 | 63 | // Prepare query 64 | $query = []; 65 | 66 | // Prepare URI 67 | $uri = []; 68 | if (null === $v = $this->emailAddress) { 69 | throw new InvalidArgument(\sprintf('Missing parameter "EmailAddress" for "%s". The value cannot be null.', __CLASS__)); 70 | } 71 | $uri['EmailAddress'] = $v; 72 | $uriString = '/v2/email/suppression/addresses/' . rawurlencode($uri['EmailAddress']); 73 | 74 | // Prepare Body 75 | $body = ''; 76 | 77 | // Return the Request 78 | return new Request('GET', $uriString, $query, $headers, StreamFactory::create($body)); 79 | } 80 | 81 | public function setEmailAddress(?string $value): self 82 | { 83 | $this->emailAddress = $value; 84 | 85 | return $this; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Input/SendEmailRequest.php: -------------------------------------------------------------------------------- 1 | , 142 | * ConfigurationSetName?: null|string, 143 | * EndpointId?: null|string, 144 | * ListManagementOptions?: null|ListManagementOptions|array, 145 | * '@region'?: string|null, 146 | * } $input 147 | */ 148 | public function __construct(array $input = []) 149 | { 150 | $this->fromEmailAddress = $input['FromEmailAddress'] ?? null; 151 | $this->fromEmailAddressIdentityArn = $input['FromEmailAddressIdentityArn'] ?? null; 152 | $this->destination = isset($input['Destination']) ? Destination::create($input['Destination']) : null; 153 | $this->replyToAddresses = $input['ReplyToAddresses'] ?? null; 154 | $this->feedbackForwardingEmailAddress = $input['FeedbackForwardingEmailAddress'] ?? null; 155 | $this->feedbackForwardingEmailAddressIdentityArn = $input['FeedbackForwardingEmailAddressIdentityArn'] ?? null; 156 | $this->content = isset($input['Content']) ? EmailContent::create($input['Content']) : null; 157 | $this->emailTags = isset($input['EmailTags']) ? array_map([MessageTag::class, 'create'], $input['EmailTags']) : null; 158 | $this->configurationSetName = $input['ConfigurationSetName'] ?? null; 159 | $this->endpointId = $input['EndpointId'] ?? null; 160 | $this->listManagementOptions = isset($input['ListManagementOptions']) ? ListManagementOptions::create($input['ListManagementOptions']) : null; 161 | parent::__construct($input); 162 | } 163 | 164 | /** 165 | * @param array{ 166 | * FromEmailAddress?: null|string, 167 | * FromEmailAddressIdentityArn?: null|string, 168 | * Destination?: null|Destination|array, 169 | * ReplyToAddresses?: null|string[], 170 | * FeedbackForwardingEmailAddress?: null|string, 171 | * FeedbackForwardingEmailAddressIdentityArn?: null|string, 172 | * Content?: EmailContent|array, 173 | * EmailTags?: null|array, 174 | * ConfigurationSetName?: null|string, 175 | * EndpointId?: null|string, 176 | * ListManagementOptions?: null|ListManagementOptions|array, 177 | * '@region'?: string|null, 178 | * }|SendEmailRequest $input 179 | */ 180 | public static function create($input): self 181 | { 182 | return $input instanceof self ? $input : new self($input); 183 | } 184 | 185 | public function getConfigurationSetName(): ?string 186 | { 187 | return $this->configurationSetName; 188 | } 189 | 190 | public function getContent(): ?EmailContent 191 | { 192 | return $this->content; 193 | } 194 | 195 | public function getDestination(): ?Destination 196 | { 197 | return $this->destination; 198 | } 199 | 200 | /** 201 | * @return MessageTag[] 202 | */ 203 | public function getEmailTags(): array 204 | { 205 | return $this->emailTags ?? []; 206 | } 207 | 208 | public function getEndpointId(): ?string 209 | { 210 | return $this->endpointId; 211 | } 212 | 213 | public function getFeedbackForwardingEmailAddress(): ?string 214 | { 215 | return $this->feedbackForwardingEmailAddress; 216 | } 217 | 218 | public function getFeedbackForwardingEmailAddressIdentityArn(): ?string 219 | { 220 | return $this->feedbackForwardingEmailAddressIdentityArn; 221 | } 222 | 223 | public function getFromEmailAddress(): ?string 224 | { 225 | return $this->fromEmailAddress; 226 | } 227 | 228 | public function getFromEmailAddressIdentityArn(): ?string 229 | { 230 | return $this->fromEmailAddressIdentityArn; 231 | } 232 | 233 | public function getListManagementOptions(): ?ListManagementOptions 234 | { 235 | return $this->listManagementOptions; 236 | } 237 | 238 | /** 239 | * @return string[] 240 | */ 241 | public function getReplyToAddresses(): array 242 | { 243 | return $this->replyToAddresses ?? []; 244 | } 245 | 246 | /** 247 | * @internal 248 | */ 249 | public function request(): Request 250 | { 251 | // Prepare headers 252 | $headers = [ 253 | 'Content-Type' => 'application/json', 254 | 'Accept' => 'application/json', 255 | ]; 256 | 257 | // Prepare query 258 | $query = []; 259 | 260 | // Prepare URI 261 | $uriString = '/v2/email/outbound-emails'; 262 | 263 | // Prepare Body 264 | $bodyPayload = $this->requestBody(); 265 | $body = empty($bodyPayload) ? '{}' : json_encode($bodyPayload, 4194304); 266 | 267 | // Return the Request 268 | return new Request('POST', $uriString, $query, $headers, StreamFactory::create($body)); 269 | } 270 | 271 | public function setConfigurationSetName(?string $value): self 272 | { 273 | $this->configurationSetName = $value; 274 | 275 | return $this; 276 | } 277 | 278 | public function setContent(?EmailContent $value): self 279 | { 280 | $this->content = $value; 281 | 282 | return $this; 283 | } 284 | 285 | public function setDestination(?Destination $value): self 286 | { 287 | $this->destination = $value; 288 | 289 | return $this; 290 | } 291 | 292 | /** 293 | * @param MessageTag[] $value 294 | */ 295 | public function setEmailTags(array $value): self 296 | { 297 | $this->emailTags = $value; 298 | 299 | return $this; 300 | } 301 | 302 | public function setEndpointId(?string $value): self 303 | { 304 | $this->endpointId = $value; 305 | 306 | return $this; 307 | } 308 | 309 | public function setFeedbackForwardingEmailAddress(?string $value): self 310 | { 311 | $this->feedbackForwardingEmailAddress = $value; 312 | 313 | return $this; 314 | } 315 | 316 | public function setFeedbackForwardingEmailAddressIdentityArn(?string $value): self 317 | { 318 | $this->feedbackForwardingEmailAddressIdentityArn = $value; 319 | 320 | return $this; 321 | } 322 | 323 | public function setFromEmailAddress(?string $value): self 324 | { 325 | $this->fromEmailAddress = $value; 326 | 327 | return $this; 328 | } 329 | 330 | public function setFromEmailAddressIdentityArn(?string $value): self 331 | { 332 | $this->fromEmailAddressIdentityArn = $value; 333 | 334 | return $this; 335 | } 336 | 337 | public function setListManagementOptions(?ListManagementOptions $value): self 338 | { 339 | $this->listManagementOptions = $value; 340 | 341 | return $this; 342 | } 343 | 344 | /** 345 | * @param string[] $value 346 | */ 347 | public function setReplyToAddresses(array $value): self 348 | { 349 | $this->replyToAddresses = $value; 350 | 351 | return $this; 352 | } 353 | 354 | private function requestBody(): array 355 | { 356 | $payload = []; 357 | if (null !== $v = $this->fromEmailAddress) { 358 | $payload['FromEmailAddress'] = $v; 359 | } 360 | if (null !== $v = $this->fromEmailAddressIdentityArn) { 361 | $payload['FromEmailAddressIdentityArn'] = $v; 362 | } 363 | if (null !== $v = $this->destination) { 364 | $payload['Destination'] = $v->requestBody(); 365 | } 366 | if (null !== $v = $this->replyToAddresses) { 367 | $index = -1; 368 | $payload['ReplyToAddresses'] = []; 369 | foreach ($v as $listValue) { 370 | ++$index; 371 | $payload['ReplyToAddresses'][$index] = $listValue; 372 | } 373 | } 374 | if (null !== $v = $this->feedbackForwardingEmailAddress) { 375 | $payload['FeedbackForwardingEmailAddress'] = $v; 376 | } 377 | if (null !== $v = $this->feedbackForwardingEmailAddressIdentityArn) { 378 | $payload['FeedbackForwardingEmailAddressIdentityArn'] = $v; 379 | } 380 | if (null === $v = $this->content) { 381 | throw new InvalidArgument(\sprintf('Missing parameter "Content" for "%s". The value cannot be null.', __CLASS__)); 382 | } 383 | $payload['Content'] = $v->requestBody(); 384 | if (null !== $v = $this->emailTags) { 385 | $index = -1; 386 | $payload['EmailTags'] = []; 387 | foreach ($v as $listValue) { 388 | ++$index; 389 | $payload['EmailTags'][$index] = $listValue->requestBody(); 390 | } 391 | } 392 | if (null !== $v = $this->configurationSetName) { 393 | $payload['ConfigurationSetName'] = $v; 394 | } 395 | if (null !== $v = $this->endpointId) { 396 | $payload['EndpointId'] = $v; 397 | } 398 | if (null !== $v = $this->listManagementOptions) { 399 | $payload['ListManagementOptions'] = $v->requestBody(); 400 | } 401 | 402 | return $payload; 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /src/Result/DeleteSuppressedDestinationResponse.php: -------------------------------------------------------------------------------- 1 | initialize(); 25 | 26 | return $this->suppressedDestination; 27 | } 28 | 29 | protected function populateResult(Response $response): void 30 | { 31 | $data = $response->toArray(); 32 | 33 | $this->suppressedDestination = $this->populateResultSuppressedDestination($data['SuppressedDestination']); 34 | } 35 | 36 | private function populateResultSuppressedDestination(array $json): SuppressedDestination 37 | { 38 | return new SuppressedDestination([ 39 | 'EmailAddress' => (string) $json['EmailAddress'], 40 | 'Reason' => (string) $json['Reason'], 41 | 'LastUpdateTime' => /** @var \DateTimeImmutable $d */ $d = \DateTimeImmutable::createFromFormat('U.u', \sprintf('%.6F', $json['LastUpdateTime'])), 42 | 'Attributes' => empty($json['Attributes']) ? null : $this->populateResultSuppressedDestinationAttributes($json['Attributes']), 43 | ]); 44 | } 45 | 46 | private function populateResultSuppressedDestinationAttributes(array $json): SuppressedDestinationAttributes 47 | { 48 | return new SuppressedDestinationAttributes([ 49 | 'MessageId' => isset($json['MessageId']) ? (string) $json['MessageId'] : null, 50 | 'FeedbackId' => isset($json['FeedbackId']) ? (string) $json['FeedbackId'] : null, 51 | ]); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Result/SendEmailResponse.php: -------------------------------------------------------------------------------- 1 | It's possible for Amazon SES to accept a message without sending it. For example, this can happen when the message 17 | * > that you're trying to send has an attachment that contains a virus, or when you send a templated email that 18 | * > contains invalid personalization content. 19 | * 20 | * @var string|null 21 | */ 22 | private $messageId; 23 | 24 | public function getMessageId(): ?string 25 | { 26 | $this->initialize(); 27 | 28 | return $this->messageId; 29 | } 30 | 31 | protected function populateResult(Response $response): void 32 | { 33 | $data = $response->toArray(); 34 | 35 | $this->messageId = isset($data['MessageId']) ? (string) $data['MessageId'] : null; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/SesClient.php: -------------------------------------------------------------------------------- 1 | getResponse($input->request(), new RequestContext(['operation' => 'DeleteSuppressedDestination', 'region' => $input->getRegion(), 'exceptionMapping' => [ 50 | 'BadRequestException' => BadRequestException::class, 51 | 'NotFoundException' => NotFoundException::class, 52 | 'TooManyRequestsException' => TooManyRequestsException::class, 53 | ]])); 54 | 55 | return new DeleteSuppressedDestinationResponse($response); 56 | } 57 | 58 | /** 59 | * Retrieves information about a specific email address that's on the suppression list for your account. 60 | * 61 | * @see https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_GetSuppressedDestination.html 62 | * @see https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-email-2019-09-27.html#getsuppresseddestination 63 | * 64 | * @param array{ 65 | * EmailAddress: string, 66 | * '@region'?: string|null, 67 | * }|GetSuppressedDestinationRequest $input 68 | * 69 | * @throws BadRequestException 70 | * @throws NotFoundException 71 | * @throws TooManyRequestsException 72 | */ 73 | public function getSuppressedDestination($input): GetSuppressedDestinationResponse 74 | { 75 | $input = GetSuppressedDestinationRequest::create($input); 76 | $response = $this->getResponse($input->request(), new RequestContext(['operation' => 'GetSuppressedDestination', 'region' => $input->getRegion(), 'exceptionMapping' => [ 77 | 'BadRequestException' => BadRequestException::class, 78 | 'NotFoundException' => NotFoundException::class, 79 | 'TooManyRequestsException' => TooManyRequestsException::class, 80 | ]])); 81 | 82 | return new GetSuppressedDestinationResponse($response); 83 | } 84 | 85 | /** 86 | * Sends an email message. You can use the Amazon SES API v2 to send the following types of messages: 87 | * 88 | * - **Simple** – A standard email message. When you create this type of message, you specify the sender, the 89 | * recipient, and the message body, and Amazon SES assembles the message for you. 90 | * - **Raw** – A raw, MIME-formatted email message. When you send this type of email, you have to specify all of the 91 | * message headers, as well as the message body. You can use this message type to send messages that contain 92 | * attachments. The message that you specify has to be a valid MIME message. 93 | * - **Templated** – A message that contains personalization tags. When you send this type of email, Amazon SES API v2 94 | * automatically replaces the tags with values that you specify. 95 | * 96 | * @see https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_SendEmail.html 97 | * @see https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-email-2019-09-27.html#sendemail 98 | * 99 | * @param array{ 100 | * FromEmailAddress?: null|string, 101 | * FromEmailAddressIdentityArn?: null|string, 102 | * Destination?: null|Destination|array, 103 | * ReplyToAddresses?: null|string[], 104 | * FeedbackForwardingEmailAddress?: null|string, 105 | * FeedbackForwardingEmailAddressIdentityArn?: null|string, 106 | * Content: EmailContent|array, 107 | * EmailTags?: null|array, 108 | * ConfigurationSetName?: null|string, 109 | * EndpointId?: null|string, 110 | * ListManagementOptions?: null|ListManagementOptions|array, 111 | * '@region'?: string|null, 112 | * }|SendEmailRequest $input 113 | * 114 | * @throws AccountSuspendedException 115 | * @throws BadRequestException 116 | * @throws LimitExceededException 117 | * @throws MailFromDomainNotVerifiedException 118 | * @throws MessageRejectedException 119 | * @throws NotFoundException 120 | * @throws SendingPausedException 121 | * @throws TooManyRequestsException 122 | */ 123 | public function sendEmail($input): SendEmailResponse 124 | { 125 | $input = SendEmailRequest::create($input); 126 | $response = $this->getResponse($input->request(), new RequestContext(['operation' => 'SendEmail', 'region' => $input->getRegion(), 'exceptionMapping' => [ 127 | 'AccountSuspendedException' => AccountSuspendedException::class, 128 | 'BadRequestException' => BadRequestException::class, 129 | 'LimitExceededException' => LimitExceededException::class, 130 | 'MailFromDomainNotVerifiedException' => MailFromDomainNotVerifiedException::class, 131 | 'MessageRejected' => MessageRejectedException::class, 132 | 'NotFoundException' => NotFoundException::class, 133 | 'SendingPausedException' => SendingPausedException::class, 134 | 'TooManyRequestsException' => TooManyRequestsException::class, 135 | ]])); 136 | 137 | return new SendEmailResponse($response); 138 | } 139 | 140 | protected function getAwsErrorFactory(): AwsErrorFactoryInterface 141 | { 142 | return new JsonRestAwsErrorFactory(); 143 | } 144 | 145 | protected function getEndpointMetadata(?string $region): array 146 | { 147 | if (null === $region) { 148 | $region = Configuration::DEFAULT_REGION; 149 | } 150 | 151 | switch ($region) { 152 | case 'fips-ca-central-1': 153 | return [ 154 | 'endpoint' => 'https://email-fips.ca-central-1.amazonaws.com', 155 | 'signRegion' => 'ca-central-1', 156 | 'signService' => 'ses', 157 | 'signVersions' => ['v4'], 158 | ]; 159 | case 'fips-us-east-1': 160 | return [ 161 | 'endpoint' => 'https://email-fips.us-east-1.amazonaws.com', 162 | 'signRegion' => 'us-east-1', 163 | 'signService' => 'ses', 164 | 'signVersions' => ['v4'], 165 | ]; 166 | case 'fips-us-east-2': 167 | return [ 168 | 'endpoint' => 'https://email-fips.us-east-2.amazonaws.com', 169 | 'signRegion' => 'us-east-2', 170 | 'signService' => 'ses', 171 | 'signVersions' => ['v4'], 172 | ]; 173 | case 'fips-us-west-1': 174 | return [ 175 | 'endpoint' => 'https://email-fips.us-west-1.amazonaws.com', 176 | 'signRegion' => 'us-west-1', 177 | 'signService' => 'ses', 178 | 'signVersions' => ['v4'], 179 | ]; 180 | case 'fips-us-west-2': 181 | return [ 182 | 'endpoint' => 'https://email-fips.us-west-2.amazonaws.com', 183 | 'signRegion' => 'us-west-2', 184 | 'signService' => 'ses', 185 | 'signVersions' => ['v4'], 186 | ]; 187 | case 'fips-us-gov-east-1': 188 | return [ 189 | 'endpoint' => 'https://email-fips.us-gov-east-1.amazonaws.com', 190 | 'signRegion' => 'us-gov-east-1', 191 | 'signService' => 'ses', 192 | 'signVersions' => ['v4'], 193 | ]; 194 | case 'fips-us-gov-west-1': 195 | return [ 196 | 'endpoint' => 'https://email-fips.us-gov-west-1.amazonaws.com', 197 | 'signRegion' => 'us-gov-west-1', 198 | 'signService' => 'ses', 199 | 'signVersions' => ['v4'], 200 | ]; 201 | } 202 | 203 | return [ 204 | 'endpoint' => "https://email.$region.amazonaws.com", 205 | 'signRegion' => $region, 206 | 'signService' => 'ses', 207 | 'signVersions' => ['v4'], 208 | ]; 209 | } 210 | 211 | protected function getServiceCode(): string 212 | { 213 | @trigger_error('Using the client with an old version of Core is deprecated. Run "composer update async-aws/core".', \E_USER_DEPRECATED); 214 | 215 | return 'email'; 216 | } 217 | 218 | protected function getSignatureScopeName(): string 219 | { 220 | @trigger_error('Using the client with an old version of Core is deprecated. Run "composer update async-aws/core".', \E_USER_DEPRECATED); 221 | 222 | return 'ses'; 223 | } 224 | 225 | protected function getSignatureVersion(): string 226 | { 227 | @trigger_error('Using the client with an old version of Core is deprecated. Run "composer update async-aws/core".', \E_USER_DEPRECATED); 228 | 229 | return 'v4'; 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/ValueObject/Attachment.php: -------------------------------------------------------------------------------- 1 | Example: `application/pdf`, `image/jpeg` 66 | * 67 | * @var string|null 68 | */ 69 | private $contentType; 70 | 71 | /** 72 | * @param array{ 73 | * RawContent: string, 74 | * ContentDisposition?: null|AttachmentContentDisposition::*, 75 | * FileName: string, 76 | * ContentDescription?: null|string, 77 | * ContentId?: null|string, 78 | * ContentTransferEncoding?: null|AttachmentContentTransferEncoding::*, 79 | * ContentType?: null|string, 80 | * } $input 81 | */ 82 | public function __construct(array $input) 83 | { 84 | $this->rawContent = $input['RawContent'] ?? $this->throwException(new InvalidArgument('Missing required field "RawContent".')); 85 | $this->contentDisposition = $input['ContentDisposition'] ?? null; 86 | $this->fileName = $input['FileName'] ?? $this->throwException(new InvalidArgument('Missing required field "FileName".')); 87 | $this->contentDescription = $input['ContentDescription'] ?? null; 88 | $this->contentId = $input['ContentId'] ?? null; 89 | $this->contentTransferEncoding = $input['ContentTransferEncoding'] ?? null; 90 | $this->contentType = $input['ContentType'] ?? null; 91 | } 92 | 93 | /** 94 | * @param array{ 95 | * RawContent: string, 96 | * ContentDisposition?: null|AttachmentContentDisposition::*, 97 | * FileName: string, 98 | * ContentDescription?: null|string, 99 | * ContentId?: null|string, 100 | * ContentTransferEncoding?: null|AttachmentContentTransferEncoding::*, 101 | * ContentType?: null|string, 102 | * }|Attachment $input 103 | */ 104 | public static function create($input): self 105 | { 106 | return $input instanceof self ? $input : new self($input); 107 | } 108 | 109 | public function getContentDescription(): ?string 110 | { 111 | return $this->contentDescription; 112 | } 113 | 114 | /** 115 | * @return AttachmentContentDisposition::*|null 116 | */ 117 | public function getContentDisposition(): ?string 118 | { 119 | return $this->contentDisposition; 120 | } 121 | 122 | public function getContentId(): ?string 123 | { 124 | return $this->contentId; 125 | } 126 | 127 | /** 128 | * @return AttachmentContentTransferEncoding::*|null 129 | */ 130 | public function getContentTransferEncoding(): ?string 131 | { 132 | return $this->contentTransferEncoding; 133 | } 134 | 135 | public function getContentType(): ?string 136 | { 137 | return $this->contentType; 138 | } 139 | 140 | public function getFileName(): string 141 | { 142 | return $this->fileName; 143 | } 144 | 145 | public function getRawContent(): string 146 | { 147 | return $this->rawContent; 148 | } 149 | 150 | /** 151 | * @internal 152 | */ 153 | public function requestBody(): array 154 | { 155 | $payload = []; 156 | $v = $this->rawContent; 157 | $payload['RawContent'] = base64_encode($v); 158 | if (null !== $v = $this->contentDisposition) { 159 | if (!AttachmentContentDisposition::exists($v)) { 160 | throw new InvalidArgument(\sprintf('Invalid parameter "ContentDisposition" for "%s". The value "%s" is not a valid "AttachmentContentDisposition".', __CLASS__, $v)); 161 | } 162 | $payload['ContentDisposition'] = $v; 163 | } 164 | $v = $this->fileName; 165 | $payload['FileName'] = $v; 166 | if (null !== $v = $this->contentDescription) { 167 | $payload['ContentDescription'] = $v; 168 | } 169 | if (null !== $v = $this->contentId) { 170 | $payload['ContentId'] = $v; 171 | } 172 | if (null !== $v = $this->contentTransferEncoding) { 173 | if (!AttachmentContentTransferEncoding::exists($v)) { 174 | throw new InvalidArgument(\sprintf('Invalid parameter "ContentTransferEncoding" for "%s". The value "%s" is not a valid "AttachmentContentTransferEncoding".', __CLASS__, $v)); 175 | } 176 | $payload['ContentTransferEncoding'] = $v; 177 | } 178 | if (null !== $v = $this->contentType) { 179 | $payload['ContentType'] = $v; 180 | } 181 | 182 | return $payload; 183 | } 184 | 185 | /** 186 | * @return never 187 | */ 188 | private function throwException(\Throwable $exception) 189 | { 190 | throw $exception; 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/ValueObject/Body.php: -------------------------------------------------------------------------------- 1 | text = isset($input['Text']) ? Content::create($input['Text']) : null; 35 | $this->html = isset($input['Html']) ? Content::create($input['Html']) : null; 36 | } 37 | 38 | /** 39 | * @param array{ 40 | * Text?: null|Content|array, 41 | * Html?: null|Content|array, 42 | * }|Body $input 43 | */ 44 | public static function create($input): self 45 | { 46 | return $input instanceof self ? $input : new self($input); 47 | } 48 | 49 | public function getHtml(): ?Content 50 | { 51 | return $this->html; 52 | } 53 | 54 | public function getText(): ?Content 55 | { 56 | return $this->text; 57 | } 58 | 59 | /** 60 | * @internal 61 | */ 62 | public function requestBody(): array 63 | { 64 | $payload = []; 65 | if (null !== $v = $this->text) { 66 | $payload['Text'] = $v->requestBody(); 67 | } 68 | if (null !== $v = $this->html) { 69 | $payload['Html'] = $v->requestBody(); 70 | } 71 | 72 | return $payload; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/ValueObject/Content.php: -------------------------------------------------------------------------------- 1 | data = $input['Data'] ?? $this->throwException(new InvalidArgument('Missing required field "Data".')); 37 | $this->charset = $input['Charset'] ?? null; 38 | } 39 | 40 | /** 41 | * @param array{ 42 | * Data: string, 43 | * Charset?: null|string, 44 | * }|Content $input 45 | */ 46 | public static function create($input): self 47 | { 48 | return $input instanceof self ? $input : new self($input); 49 | } 50 | 51 | public function getCharset(): ?string 52 | { 53 | return $this->charset; 54 | } 55 | 56 | public function getData(): string 57 | { 58 | return $this->data; 59 | } 60 | 61 | /** 62 | * @internal 63 | */ 64 | public function requestBody(): array 65 | { 66 | $payload = []; 67 | $v = $this->data; 68 | $payload['Data'] = $v; 69 | if (null !== $v = $this->charset) { 70 | $payload['Charset'] = $v; 71 | } 72 | 73 | return $payload; 74 | } 75 | 76 | /** 77 | * @return never 78 | */ 79 | private function throwException(\Throwable $exception) 80 | { 81 | throw $exception; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/ValueObject/Destination.php: -------------------------------------------------------------------------------- 1 | Amazon SES does not support the SMTPUTF8 extension, as described in RFC6531 [^1]. For this reason, the *local part* 9 | * > of a destination email address (the part of the email address that precedes the @ sign) may only contain 7-bit 10 | * > ASCII characters [^2]. If the *domain part* of an address (the part after the @ sign) contains non-ASCII 11 | * > characters, they must be encoded using Punycode, as described in RFC3492 [^3]. 12 | * 13 | * [^1]: https://tools.ietf.org/html/rfc6531 14 | * [^2]: https://en.wikipedia.org/wiki/Email_address#Local-part 15 | * [^3]: https://tools.ietf.org/html/rfc3492.html 16 | */ 17 | final class Destination 18 | { 19 | /** 20 | * An array that contains the email addresses of the "To" recipients for the email. 21 | * 22 | * @var string[]|null 23 | */ 24 | private $toAddresses; 25 | 26 | /** 27 | * An array that contains the email addresses of the "CC" (carbon copy) recipients for the email. 28 | * 29 | * @var string[]|null 30 | */ 31 | private $ccAddresses; 32 | 33 | /** 34 | * An array that contains the email addresses of the "BCC" (blind carbon copy) recipients for the email. 35 | * 36 | * @var string[]|null 37 | */ 38 | private $bccAddresses; 39 | 40 | /** 41 | * @param array{ 42 | * ToAddresses?: null|string[], 43 | * CcAddresses?: null|string[], 44 | * BccAddresses?: null|string[], 45 | * } $input 46 | */ 47 | public function __construct(array $input) 48 | { 49 | $this->toAddresses = $input['ToAddresses'] ?? null; 50 | $this->ccAddresses = $input['CcAddresses'] ?? null; 51 | $this->bccAddresses = $input['BccAddresses'] ?? null; 52 | } 53 | 54 | /** 55 | * @param array{ 56 | * ToAddresses?: null|string[], 57 | * CcAddresses?: null|string[], 58 | * BccAddresses?: null|string[], 59 | * }|Destination $input 60 | */ 61 | public static function create($input): self 62 | { 63 | return $input instanceof self ? $input : new self($input); 64 | } 65 | 66 | /** 67 | * @return string[] 68 | */ 69 | public function getBccAddresses(): array 70 | { 71 | return $this->bccAddresses ?? []; 72 | } 73 | 74 | /** 75 | * @return string[] 76 | */ 77 | public function getCcAddresses(): array 78 | { 79 | return $this->ccAddresses ?? []; 80 | } 81 | 82 | /** 83 | * @return string[] 84 | */ 85 | public function getToAddresses(): array 86 | { 87 | return $this->toAddresses ?? []; 88 | } 89 | 90 | /** 91 | * @internal 92 | */ 93 | public function requestBody(): array 94 | { 95 | $payload = []; 96 | if (null !== $v = $this->toAddresses) { 97 | $index = -1; 98 | $payload['ToAddresses'] = []; 99 | foreach ($v as $listValue) { 100 | ++$index; 101 | $payload['ToAddresses'][$index] = $listValue; 102 | } 103 | } 104 | if (null !== $v = $this->ccAddresses) { 105 | $index = -1; 106 | $payload['CcAddresses'] = []; 107 | foreach ($v as $listValue) { 108 | ++$index; 109 | $payload['CcAddresses'][$index] = $listValue; 110 | } 111 | } 112 | if (null !== $v = $this->bccAddresses) { 113 | $index = -1; 114 | $payload['BccAddresses'] = []; 115 | foreach ($v as $listValue) { 116 | ++$index; 117 | $payload['BccAddresses'][$index] = $listValue; 118 | } 119 | } 120 | 121 | return $payload; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/ValueObject/EmailContent.php: -------------------------------------------------------------------------------- 1 | simple = isset($input['Simple']) ? Message::create($input['Simple']) : null; 58 | $this->raw = isset($input['Raw']) ? RawMessage::create($input['Raw']) : null; 59 | $this->template = isset($input['Template']) ? Template::create($input['Template']) : null; 60 | } 61 | 62 | /** 63 | * @param array{ 64 | * Simple?: null|Message|array, 65 | * Raw?: null|RawMessage|array, 66 | * Template?: null|Template|array, 67 | * }|EmailContent $input 68 | */ 69 | public static function create($input): self 70 | { 71 | return $input instanceof self ? $input : new self($input); 72 | } 73 | 74 | public function getRaw(): ?RawMessage 75 | { 76 | return $this->raw; 77 | } 78 | 79 | public function getSimple(): ?Message 80 | { 81 | return $this->simple; 82 | } 83 | 84 | public function getTemplate(): ?Template 85 | { 86 | return $this->template; 87 | } 88 | 89 | /** 90 | * @internal 91 | */ 92 | public function requestBody(): array 93 | { 94 | $payload = []; 95 | if (null !== $v = $this->simple) { 96 | $payload['Simple'] = $v->requestBody(); 97 | } 98 | if (null !== $v = $this->raw) { 99 | $payload['Raw'] = $v->requestBody(); 100 | } 101 | if (null !== $v = $this->template) { 102 | $payload['Template'] = $v->requestBody(); 103 | } 104 | 105 | return $payload; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/ValueObject/EmailTemplateContent.php: -------------------------------------------------------------------------------- 1 | subject = $input['Subject'] ?? null; 41 | $this->text = $input['Text'] ?? null; 42 | $this->html = $input['Html'] ?? null; 43 | } 44 | 45 | /** 46 | * @param array{ 47 | * Subject?: null|string, 48 | * Text?: null|string, 49 | * Html?: null|string, 50 | * }|EmailTemplateContent $input 51 | */ 52 | public static function create($input): self 53 | { 54 | return $input instanceof self ? $input : new self($input); 55 | } 56 | 57 | public function getHtml(): ?string 58 | { 59 | return $this->html; 60 | } 61 | 62 | public function getSubject(): ?string 63 | { 64 | return $this->subject; 65 | } 66 | 67 | public function getText(): ?string 68 | { 69 | return $this->text; 70 | } 71 | 72 | /** 73 | * @internal 74 | */ 75 | public function requestBody(): array 76 | { 77 | $payload = []; 78 | if (null !== $v = $this->subject) { 79 | $payload['Subject'] = $v; 80 | } 81 | if (null !== $v = $this->text) { 82 | $payload['Text'] = $v; 83 | } 84 | if (null !== $v = $this->html) { 85 | $payload['Html'] = $v; 86 | } 87 | 88 | return $payload; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/ValueObject/ListManagementOptions.php: -------------------------------------------------------------------------------- 1 | contactListName = $input['ContactListName'] ?? $this->throwException(new InvalidArgument('Missing required field "ContactListName".')); 36 | $this->topicName = $input['TopicName'] ?? null; 37 | } 38 | 39 | /** 40 | * @param array{ 41 | * ContactListName: string, 42 | * TopicName?: null|string, 43 | * }|ListManagementOptions $input 44 | */ 45 | public static function create($input): self 46 | { 47 | return $input instanceof self ? $input : new self($input); 48 | } 49 | 50 | public function getContactListName(): string 51 | { 52 | return $this->contactListName; 53 | } 54 | 55 | public function getTopicName(): ?string 56 | { 57 | return $this->topicName; 58 | } 59 | 60 | /** 61 | * @internal 62 | */ 63 | public function requestBody(): array 64 | { 65 | $payload = []; 66 | $v = $this->contactListName; 67 | $payload['ContactListName'] = $v; 68 | if (null !== $v = $this->topicName) { 69 | $payload['TopicName'] = $v; 70 | } 71 | 72 | return $payload; 73 | } 74 | 75 | /** 76 | * @return never 77 | */ 78 | private function throwException(\Throwable $exception) 79 | { 80 | throw $exception; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/ValueObject/Message.php: -------------------------------------------------------------------------------- 1 | , 48 | * Attachments?: null|array, 49 | * } $input 50 | */ 51 | public function __construct(array $input) 52 | { 53 | $this->subject = isset($input['Subject']) ? Content::create($input['Subject']) : $this->throwException(new InvalidArgument('Missing required field "Subject".')); 54 | $this->body = isset($input['Body']) ? Body::create($input['Body']) : $this->throwException(new InvalidArgument('Missing required field "Body".')); 55 | $this->headers = isset($input['Headers']) ? array_map([MessageHeader::class, 'create'], $input['Headers']) : null; 56 | $this->attachments = isset($input['Attachments']) ? array_map([Attachment::class, 'create'], $input['Attachments']) : null; 57 | } 58 | 59 | /** 60 | * @param array{ 61 | * Subject: Content|array, 62 | * Body: Body|array, 63 | * Headers?: null|array, 64 | * Attachments?: null|array, 65 | * }|Message $input 66 | */ 67 | public static function create($input): self 68 | { 69 | return $input instanceof self ? $input : new self($input); 70 | } 71 | 72 | /** 73 | * @return Attachment[] 74 | */ 75 | public function getAttachments(): array 76 | { 77 | return $this->attachments ?? []; 78 | } 79 | 80 | public function getBody(): Body 81 | { 82 | return $this->body; 83 | } 84 | 85 | /** 86 | * @return MessageHeader[] 87 | */ 88 | public function getHeaders(): array 89 | { 90 | return $this->headers ?? []; 91 | } 92 | 93 | public function getSubject(): Content 94 | { 95 | return $this->subject; 96 | } 97 | 98 | /** 99 | * @internal 100 | */ 101 | public function requestBody(): array 102 | { 103 | $payload = []; 104 | $v = $this->subject; 105 | $payload['Subject'] = $v->requestBody(); 106 | $v = $this->body; 107 | $payload['Body'] = $v->requestBody(); 108 | if (null !== $v = $this->headers) { 109 | $index = -1; 110 | $payload['Headers'] = []; 111 | foreach ($v as $listValue) { 112 | ++$index; 113 | $payload['Headers'][$index] = $listValue->requestBody(); 114 | } 115 | } 116 | if (null !== $v = $this->attachments) { 117 | $index = -1; 118 | $payload['Attachments'] = []; 119 | foreach ($v as $listValue) { 120 | ++$index; 121 | $payload['Attachments'][$index] = $listValue->requestBody(); 122 | } 123 | } 124 | 125 | return $payload; 126 | } 127 | 128 | /** 129 | * @return never 130 | */ 131 | private function throwException(\Throwable $exception) 132 | { 133 | throw $exception; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/ValueObject/MessageHeader.php: -------------------------------------------------------------------------------- 1 | name = $input['Name'] ?? $this->throwException(new InvalidArgument('Missing required field "Name".')); 41 | $this->value = $input['Value'] ?? $this->throwException(new InvalidArgument('Missing required field "Value".')); 42 | } 43 | 44 | /** 45 | * @param array{ 46 | * Name: string, 47 | * Value: string, 48 | * }|MessageHeader $input 49 | */ 50 | public static function create($input): self 51 | { 52 | return $input instanceof self ? $input : new self($input); 53 | } 54 | 55 | public function getName(): string 56 | { 57 | return $this->name; 58 | } 59 | 60 | public function getValue(): string 61 | { 62 | return $this->value; 63 | } 64 | 65 | /** 66 | * @internal 67 | */ 68 | public function requestBody(): array 69 | { 70 | $payload = []; 71 | $v = $this->name; 72 | $payload['Name'] = $v; 73 | $v = $this->value; 74 | $payload['Value'] = $v; 75 | 76 | return $payload; 77 | } 78 | 79 | /** 80 | * @return never 81 | */ 82 | private function throwException(\Throwable $exception) 83 | { 84 | throw $exception; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/ValueObject/MessageTag.php: -------------------------------------------------------------------------------- 1 | name = $input['Name'] ?? $this->throwException(new InvalidArgument('Missing required field "Name".')); 42 | $this->value = $input['Value'] ?? $this->throwException(new InvalidArgument('Missing required field "Value".')); 43 | } 44 | 45 | /** 46 | * @param array{ 47 | * Name: string, 48 | * Value: string, 49 | * }|MessageTag $input 50 | */ 51 | public static function create($input): self 52 | { 53 | return $input instanceof self ? $input : new self($input); 54 | } 55 | 56 | public function getName(): string 57 | { 58 | return $this->name; 59 | } 60 | 61 | public function getValue(): string 62 | { 63 | return $this->value; 64 | } 65 | 66 | /** 67 | * @internal 68 | */ 69 | public function requestBody(): array 70 | { 71 | $payload = []; 72 | $v = $this->name; 73 | $payload['Name'] = $v; 74 | $v = $this->value; 75 | $payload['Value'] = $v; 76 | 77 | return $payload; 78 | } 79 | 80 | /** 81 | * @return never 82 | */ 83 | private function throwException(\Throwable $exception) 84 | { 85 | throw $exception; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/ValueObject/RawMessage.php: -------------------------------------------------------------------------------- 1 | data = $input['Data'] ?? $this->throwException(new InvalidArgument('Missing required field "Data".')); 41 | } 42 | 43 | /** 44 | * @param array{ 45 | * Data: string, 46 | * }|RawMessage $input 47 | */ 48 | public static function create($input): self 49 | { 50 | return $input instanceof self ? $input : new self($input); 51 | } 52 | 53 | public function getData(): string 54 | { 55 | return $this->data; 56 | } 57 | 58 | /** 59 | * @internal 60 | */ 61 | public function requestBody(): array 62 | { 63 | $payload = []; 64 | $v = $this->data; 65 | $payload['Data'] = base64_encode($v); 66 | 67 | return $payload; 68 | } 69 | 70 | /** 71 | * @return never 72 | */ 73 | private function throwException(\Throwable $exception) 74 | { 75 | throw $exception; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/ValueObject/SuppressedDestination.php: -------------------------------------------------------------------------------- 1 | emailAddress = $input['EmailAddress'] ?? $this->throwException(new InvalidArgument('Missing required field "EmailAddress".')); 53 | $this->reason = $input['Reason'] ?? $this->throwException(new InvalidArgument('Missing required field "Reason".')); 54 | $this->lastUpdateTime = $input['LastUpdateTime'] ?? $this->throwException(new InvalidArgument('Missing required field "LastUpdateTime".')); 55 | $this->attributes = isset($input['Attributes']) ? SuppressedDestinationAttributes::create($input['Attributes']) : null; 56 | } 57 | 58 | /** 59 | * @param array{ 60 | * EmailAddress: string, 61 | * Reason: SuppressionListReason::*, 62 | * LastUpdateTime: \DateTimeImmutable, 63 | * Attributes?: null|SuppressedDestinationAttributes|array, 64 | * }|SuppressedDestination $input 65 | */ 66 | public static function create($input): self 67 | { 68 | return $input instanceof self ? $input : new self($input); 69 | } 70 | 71 | public function getAttributes(): ?SuppressedDestinationAttributes 72 | { 73 | return $this->attributes; 74 | } 75 | 76 | public function getEmailAddress(): string 77 | { 78 | return $this->emailAddress; 79 | } 80 | 81 | public function getLastUpdateTime(): \DateTimeImmutable 82 | { 83 | return $this->lastUpdateTime; 84 | } 85 | 86 | /** 87 | * @return SuppressionListReason::* 88 | */ 89 | public function getReason(): string 90 | { 91 | return $this->reason; 92 | } 93 | 94 | /** 95 | * @return never 96 | */ 97 | private function throwException(\Throwable $exception) 98 | { 99 | throw $exception; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/ValueObject/SuppressedDestinationAttributes.php: -------------------------------------------------------------------------------- 1 | messageId = $input['MessageId'] ?? null; 35 | $this->feedbackId = $input['FeedbackId'] ?? null; 36 | } 37 | 38 | /** 39 | * @param array{ 40 | * MessageId?: null|string, 41 | * FeedbackId?: null|string, 42 | * }|SuppressedDestinationAttributes $input 43 | */ 44 | public static function create($input): self 45 | { 46 | return $input instanceof self ? $input : new self($input); 47 | } 48 | 49 | public function getFeedbackId(): ?string 50 | { 51 | return $this->feedbackId; 52 | } 53 | 54 | public function getMessageId(): ?string 55 | { 56 | return $this->messageId; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/ValueObject/Template.php: -------------------------------------------------------------------------------- 1 | Amazon SES supports only simple substitions when you send email using the `SendEmail` or `SendBulkEmail` operations 32 | * > and you provide the full template content in the request. 33 | * 34 | * @var EmailTemplateContent|null 35 | */ 36 | private $templateContent; 37 | 38 | /** 39 | * An object that defines the values to use for message variables in the template. This object is a set of key-value 40 | * pairs. Each key defines a message variable in the template. The corresponding value defines the value to use for that 41 | * variable. 42 | * 43 | * @var string|null 44 | */ 45 | private $templateData; 46 | 47 | /** 48 | * The list of message headers that will be added to the email message. 49 | * 50 | * @var MessageHeader[]|null 51 | */ 52 | private $headers; 53 | 54 | /** 55 | * The List of attachments to include in your email. All recipients will receive the same attachments. 56 | * 57 | * @var Attachment[]|null 58 | */ 59 | private $attachments; 60 | 61 | /** 62 | * @param array{ 63 | * TemplateName?: null|string, 64 | * TemplateArn?: null|string, 65 | * TemplateContent?: null|EmailTemplateContent|array, 66 | * TemplateData?: null|string, 67 | * Headers?: null|array, 68 | * Attachments?: null|array, 69 | * } $input 70 | */ 71 | public function __construct(array $input) 72 | { 73 | $this->templateName = $input['TemplateName'] ?? null; 74 | $this->templateArn = $input['TemplateArn'] ?? null; 75 | $this->templateContent = isset($input['TemplateContent']) ? EmailTemplateContent::create($input['TemplateContent']) : null; 76 | $this->templateData = $input['TemplateData'] ?? null; 77 | $this->headers = isset($input['Headers']) ? array_map([MessageHeader::class, 'create'], $input['Headers']) : null; 78 | $this->attachments = isset($input['Attachments']) ? array_map([Attachment::class, 'create'], $input['Attachments']) : null; 79 | } 80 | 81 | /** 82 | * @param array{ 83 | * TemplateName?: null|string, 84 | * TemplateArn?: null|string, 85 | * TemplateContent?: null|EmailTemplateContent|array, 86 | * TemplateData?: null|string, 87 | * Headers?: null|array, 88 | * Attachments?: null|array, 89 | * }|Template $input 90 | */ 91 | public static function create($input): self 92 | { 93 | return $input instanceof self ? $input : new self($input); 94 | } 95 | 96 | /** 97 | * @return Attachment[] 98 | */ 99 | public function getAttachments(): array 100 | { 101 | return $this->attachments ?? []; 102 | } 103 | 104 | /** 105 | * @return MessageHeader[] 106 | */ 107 | public function getHeaders(): array 108 | { 109 | return $this->headers ?? []; 110 | } 111 | 112 | public function getTemplateArn(): ?string 113 | { 114 | return $this->templateArn; 115 | } 116 | 117 | public function getTemplateContent(): ?EmailTemplateContent 118 | { 119 | return $this->templateContent; 120 | } 121 | 122 | public function getTemplateData(): ?string 123 | { 124 | return $this->templateData; 125 | } 126 | 127 | public function getTemplateName(): ?string 128 | { 129 | return $this->templateName; 130 | } 131 | 132 | /** 133 | * @internal 134 | */ 135 | public function requestBody(): array 136 | { 137 | $payload = []; 138 | if (null !== $v = $this->templateName) { 139 | $payload['TemplateName'] = $v; 140 | } 141 | if (null !== $v = $this->templateArn) { 142 | $payload['TemplateArn'] = $v; 143 | } 144 | if (null !== $v = $this->templateContent) { 145 | $payload['TemplateContent'] = $v->requestBody(); 146 | } 147 | if (null !== $v = $this->templateData) { 148 | $payload['TemplateData'] = $v; 149 | } 150 | if (null !== $v = $this->headers) { 151 | $index = -1; 152 | $payload['Headers'] = []; 153 | foreach ($v as $listValue) { 154 | ++$index; 155 | $payload['Headers'][$index] = $listValue->requestBody(); 156 | } 157 | } 158 | if (null !== $v = $this->attachments) { 159 | $index = -1; 160 | $payload['Attachments'] = []; 161 | foreach ($v as $listValue) { 162 | ++$index; 163 | $payload['Attachments'][$index] = $listValue->requestBody(); 164 | } 165 | } 166 | 167 | return $payload; 168 | } 169 | } 170 | --------------------------------------------------------------------------------