├── .php-cs-fixer.php
├── LICENSE
├── composer.json
├── phpunit.xml
├── renovate.json
└── src
├── ApiKey.php
├── Audience.php
├── Broadcast.php
├── Client.php
├── Collection.php
├── Contact.php
├── Contracts
├── Client.php
├── Resource.php
├── Stringable.php
└── Transporter.php
├── Domain.php
├── Email.php
├── Enums
└── Transporter
│ ├── ContentType.php
│ └── Method.php
├── Exceptions
├── ErrorException.php
├── MissingAttributeException.php
├── TransporterException.php
├── UnserializableResponse.php
└── WebhookSignatureVerificationException.php
├── Resend.php
├── Resource.php
├── Service
├── ApiKey.php
├── Audience.php
├── Batch.php
├── Broadcast.php
├── Contact.php
├── Domain.php
├── Email.php
├── Service.php
└── ServiceFactory.php
├── Transporters
└── HttpTransporter.php
├── ValueObjects
├── ApiKey.php
├── ResourceUri.php
└── Transporter
│ ├── BaseUri.php
│ ├── Headers.php
│ └── Payload.php
└── WebhookSignature.php
/.php-cs-fixer.php:
--------------------------------------------------------------------------------
1 | true,
8 | 'array_syntax' => ['syntax' => 'short'],
9 | 'binary_operator_spaces' => [
10 | 'default' => 'single_space',
11 | ],
12 | 'blank_line_after_namespace' => true,
13 | 'blank_line_after_opening_tag' => true,
14 | 'blank_line_before_statement' => [
15 | 'statements' => [
16 | 'continue',
17 | 'return',
18 | ],
19 | ],
20 | 'braces' => [
21 | 'allow_single_line_anonymous_class_with_empty_body' => true,
22 | 'allow_single_line_closure' => true,
23 | 'position_after_control_structures' => 'same',
24 | 'position_after_functions_and_oop_constructs' => 'next',
25 | 'position_after_anonymous_constructs' => 'next',
26 | ],
27 | 'curly_braces_position' => [
28 | 'control_structures_opening_brace' => 'same_line',
29 | 'functions_opening_brace' => 'next_line_unless_newline_at_signature_end',
30 | 'anonymous_functions_opening_brace' => 'same_line',
31 | 'classes_opening_brace' => 'next_line_unless_newline_at_signature_end',
32 | 'anonymous_classes_opening_brace' => 'next_line_unless_newline_at_signature_end',
33 | 'allow_single_line_empty_anonymous_classes' => false,
34 | 'allow_single_line_anonymous_functions' => false,
35 | ],
36 | 'cast_spaces' => true,
37 | 'class_attributes_separation' => [
38 | 'elements' => [
39 | 'const' => 'one',
40 | 'method' => 'one',
41 | 'property' => 'one',
42 | 'trait_import' => 'none',
43 | ],
44 | ],
45 | 'class_definition' => [
46 | 'multi_line_extends_each_single_line' => true,
47 | 'single_item_single_line' => true,
48 | 'single_line' => true,
49 | ],
50 | 'clean_namespace' => true,
51 | 'compact_nullable_typehint' => true,
52 | 'concat_space' => [
53 | 'spacing' => 'one',
54 | ],
55 | 'constant_case' => ['case' => 'lower'],
56 | 'declare_equal_normalize' => true,
57 | 'elseif' => true,
58 | 'encoding' => true,
59 | 'full_opening_tag' => true,
60 | 'fully_qualified_strict_types' => true,
61 | 'function_declaration' => true,
62 | 'function_typehint_space' => true,
63 | 'general_phpdoc_tag_rename' => true,
64 | 'heredoc_to_nowdoc' => true,
65 | 'include' => true,
66 | 'increment_style' => ['style' => 'post'],
67 | 'indentation_type' => true,
68 | 'integer_literal_case' => true,
69 | 'lambda_not_used_import' => true,
70 | 'linebreak_after_opening_tag' => true,
71 | 'line_ending' => true,
72 | 'list_syntax' => true,
73 | 'lowercase_cast' => true,
74 | 'lowercase_keywords' => true,
75 | 'lowercase_static_reference' => true,
76 | 'magic_method_casing' => true,
77 | 'magic_constant_casing' => true,
78 | 'method_argument_space' => [
79 | 'on_multiline' => 'ignore',
80 | ],
81 | 'multiline_whitespace_before_semicolons' => [
82 | 'strategy' => 'no_multi_line',
83 | ],
84 | 'native_function_casing' => true,
85 | 'native_function_type_declaration_casing' => true,
86 | 'no_alias_functions' => true,
87 | 'no_alias_language_construct_call' => true,
88 | 'no_alternative_syntax' => true,
89 | 'no_binary_string' => true,
90 | 'no_blank_lines_after_class_opening' => true,
91 | 'no_blank_lines_after_phpdoc' => true,
92 | 'no_closing_tag' => true,
93 | 'no_empty_phpdoc' => true,
94 | 'no_empty_statement' => true,
95 | 'no_extra_blank_lines' => [
96 | 'tokens' => [
97 | 'extra',
98 | 'throw',
99 | 'use',
100 | ],
101 | ],
102 | 'no_leading_import_slash' => true,
103 | 'no_leading_namespace_whitespace' => true,
104 | 'no_mixed_echo_print' => [
105 | 'use' => 'echo',
106 | ],
107 | 'no_multiline_whitespace_around_double_arrow' => true,
108 | 'no_short_bool_cast' => true,
109 | 'no_singleline_whitespace_before_semicolons' => true,
110 | 'no_spaces_after_function_name' => true,
111 | 'no_space_around_double_colon' => true,
112 | 'no_spaces_around_offset' => [
113 | 'positions' => ['inside', 'outside'],
114 | ],
115 | 'no_spaces_inside_parenthesis' => true,
116 | 'no_superfluous_phpdoc_tags' => [
117 | 'allow_mixed' => true,
118 | 'allow_unused_params' => true,
119 | ],
120 | 'no_trailing_comma_in_list_call' => true,
121 | 'no_trailing_comma_in_singleline_array' => true,
122 | 'no_trailing_whitespace' => true,
123 | 'no_trailing_whitespace_in_comment' => true,
124 | 'no_unneeded_control_parentheses' => [
125 | 'statements' => ['break', 'clone', 'continue', 'echo_print', 'return', 'switch_case', 'yield'],
126 | ],
127 | 'no_unneeded_curly_braces' => true,
128 | 'no_unset_cast' => true,
129 | 'no_unused_imports' => true,
130 | 'no_unreachable_default_argument_value' => true,
131 | 'no_useless_return' => true,
132 | 'no_whitespace_before_comma_in_array' => true,
133 | 'no_whitespace_in_blank_line' => true,
134 | 'normalize_index_brace' => true,
135 | 'not_operator_with_successor_space' => true,
136 | 'object_operator_without_whitespace' => true,
137 | 'ordered_imports' => ['sort_algorithm' => 'alpha'],
138 | 'psr_autoloading' => false,
139 | 'phpdoc_indent' => true,
140 | 'phpdoc_inline_tag_normalizer' => true,
141 | 'phpdoc_no_access' => true,
142 | 'phpdoc_no_package' => true,
143 | 'phpdoc_no_useless_inheritdoc' => true,
144 | 'phpdoc_scalar' => true,
145 | 'phpdoc_single_line_var_spacing' => true,
146 | 'phpdoc_summary' => false,
147 | 'phpdoc_to_comment' => false,
148 | 'phpdoc_tag_type' => [
149 | 'tags' => [
150 | 'inheritdoc' => 'inline',
151 | ],
152 | ],
153 | 'phpdoc_trim' => true,
154 | 'phpdoc_types' => true,
155 | 'phpdoc_var_without_name' => true,
156 | 'return_type_declaration' => ['space_before' => 'none'],
157 | 'self_accessor' => false,
158 | 'short_scalar_cast' => true,
159 | 'simplified_null_return' => false,
160 | 'single_blank_line_at_eof' => true,
161 | 'single_blank_line_before_namespace' => true,
162 | 'single_class_element_per_statement' => [
163 | 'elements' => ['const', 'property'],
164 | ],
165 | 'single_import_per_statement' => true,
166 | 'single_line_after_imports' => true,
167 | 'single_line_comment_style' => [
168 | 'comment_types' => ['hash'],
169 | ],
170 | 'single_quote' => true,
171 | 'space_after_semicolon' => true,
172 | 'standardize_not_equals' => true,
173 | 'statement_indentation' => false,
174 | 'switch_case_semicolon_to_colon' => true,
175 | 'switch_case_space' => true,
176 | 'ternary_operator_spaces' => true,
177 | 'trailing_comma_in_multiline' => ['elements' => ['arrays']],
178 | 'trim_array_spaces' => true,
179 | 'types_spaces' => true,
180 | 'unary_operator_spaces' => true,
181 | 'visibility_required' => [
182 | 'elements' => ['method', 'property'],
183 | ],
184 | 'whitespace_after_comma_in_array' => true,
185 | ];
186 |
187 | $finder = Finder::create()
188 | ->name('*.php')
189 | ->exclude([
190 | 'vendor',
191 | ])
192 | ->ignoreDotFiles(true)
193 | ->ignoreVCS(true);
194 |
195 | $config = new Config();
196 |
197 | return $config->setFinder($finder)
198 | ->setRules($rules)
199 | ->setRiskyAllowed(true)
200 | ->setUsingCache(true);
201 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Plus Five Five, Inc.
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "resend/resend-php",
3 | "description": "Resend PHP library.",
4 | "keywords": ["php", "resend", "sdk", "api", "client"],
5 | "homepage": "https://resend.com/",
6 | "license": "MIT",
7 | "authors": [
8 | {
9 | "name": "Resend and contributors",
10 | "homepage": "https://github.com/resend/resend-php/contributors"
11 | }
12 | ],
13 | "require": {
14 | "php": "^8.1.0",
15 | "guzzlehttp/guzzle": "^7.5"
16 | },
17 | "require-dev": {
18 | "friendsofphp/php-cs-fixer": "^3.13",
19 | "mockery/mockery": "^1.6",
20 | "pestphp/pest": "^2.0"
21 | },
22 | "autoload": {
23 | "psr-4": {
24 | "Resend\\": "src/"
25 | },
26 | "files": [
27 | "src/Resend.php"
28 | ]
29 | },
30 | "autoload-dev": {
31 | "psr-4": {
32 | "Tests\\": "tests/"
33 | }
34 | },
35 | "config": {
36 | "sort-packages": true,
37 | "preferred-install": "dist",
38 | "allow-plugins": {
39 | "pestphp/pest-plugin": true
40 | }
41 | },
42 | "minimum-stability": "dev",
43 | "prefer-stable": true
44 | }
45 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 | ./tests
11 |
12 |
13 |
14 |
15 | ./src
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "config:base"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/src/ApiKey.php:
--------------------------------------------------------------------------------
1 | emails->send($parameters);
45 | }
46 |
47 | /**
48 | * Magic method to retrieve a service by name.
49 | */
50 | public function __get(string $name)
51 | {
52 | return $this->getService($name);
53 | }
54 |
55 | /**
56 | * Magic method to retrieve a service by name.
57 | */
58 | public function __call(string $name, array $arguments)
59 | {
60 | return $this->getService($name);
61 | }
62 |
63 | /**
64 | * Attach the given API service to the client.
65 | */
66 | private function getService(string $name)
67 | {
68 | if (! isset($this->serviceFactory)) {
69 | $this->serviceFactory = new ServiceFactory($this->transporter);
70 | }
71 |
72 | return $this->serviceFactory->getService($name);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Collection.php:
--------------------------------------------------------------------------------
1 |
13 | *
14 | * @property TResource[] $data
15 | */
16 | class Collection extends Resource implements IteratorAggregate
17 | {
18 | /**
19 | * {@inheritdoc}
20 | */
21 | public function getIterator(): Traversable
22 | {
23 | return new ArrayIterator($this->data);
24 | }
25 |
26 | /**
27 | * {@inheritdoc}
28 | */
29 | public function offsetGet(mixed $offset): mixed
30 | {
31 | if (is_string($offset)) {
32 | return parent::offsetGet($offset);
33 | }
34 |
35 | throw new InvalidArgumentException("You tried to access the {$offset} index, but Collection types only support string keys.");
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Contact.php:
--------------------------------------------------------------------------------
1 |
16 | *
17 | * @throws ErrorException|TransporterException|UnserializableResponse
18 | */
19 | public function request(Payload $payload): array;
20 | }
21 |
--------------------------------------------------------------------------------
/src/Domain.php:
--------------------------------------------------------------------------------
1 | getMessage();
23 | }
24 |
25 | /**
26 | * Get the error type
27 | */
28 | public function getErrorType(): string
29 | {
30 | return $this->contents['type'];
31 | }
32 |
33 | /**
34 | * Get the error code.
35 | */
36 | public function getErrorCode(): int
37 | {
38 | return $this->contents['code'];
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Exceptions/MissingAttributeException.php:
--------------------------------------------------------------------------------
1 | getMessage(), 0, $exception);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Exceptions/UnserializableResponse.php:
--------------------------------------------------------------------------------
1 | getMessage(), 0, $exception);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Exceptions/WebhookSignatureVerificationException.php:
--------------------------------------------------------------------------------
1 | fill($attributes);
22 | }
23 |
24 | /**
25 | * Create a new resource from the given attributes.
26 | */
27 | public static function from(array $attributes): static
28 | {
29 | return new static($attributes);
30 | }
31 |
32 | /**
33 | * Fill the resource with an array of attributes.
34 | */
35 | protected function fill(array $attributes): void
36 | {
37 | $fillable = $attributes;
38 |
39 | foreach ($fillable as $key => $value) {
40 | $this->attributes[$key] = $value;
41 | }
42 | }
43 |
44 | /**
45 | * Get an attribute by name.
46 | */
47 | public function getAttribute($name)
48 | {
49 | if (! $name) {
50 | return;
51 | }
52 |
53 | if (array_key_exists($name, $this->attributes)) {
54 | return $this->getAttributes()[$name] ?? null;
55 | }
56 |
57 | return null;
58 | }
59 |
60 | /**
61 | * Get all attributes for the resource.
62 | */
63 | public function getAttributes(): array
64 | {
65 | return $this->attributes;
66 | }
67 |
68 | /**
69 | * Convert the resource instance into an array.
70 | */
71 | public function toArray(): array
72 | {
73 | return $this->getAttributes();
74 | }
75 |
76 | /**
77 | * Convert the resource instance into JSON.
78 | */
79 | public function toJson(int $options = 0): string
80 | {
81 | $json = json_encode($this->jsonSerialize(), $options);
82 |
83 | return $json;
84 | }
85 |
86 | /**
87 | * Convert the object into something JSON serializable.
88 | */
89 | public function jsonSerialize(): mixed
90 | {
91 | return $this->toArray();
92 | }
93 |
94 | /**
95 | * Dynamically retrieve attributes on the resource.
96 | */
97 | public function __get($name)
98 | {
99 | return $this->getAttribute($name);
100 | }
101 |
102 | /**
103 | * Get all the attributes when dumping the resource.
104 | */
105 | public function __debugInfo(): array
106 | {
107 | return $this->getAttributes();
108 | }
109 |
110 | /**
111 | * {@inheritdoc}
112 | */
113 | public function offsetExists(mixed $offset): bool
114 | {
115 | try {
116 | return ! is_null($this->getAttribute($offset));
117 | } catch (MissingAttributeException) {
118 | return false;
119 | }
120 | }
121 |
122 | /**
123 | * {@inheritdoc}
124 | */
125 | public function offsetGet(mixed $offset): mixed
126 | {
127 | return $this->getAttribute($offset);
128 | }
129 |
130 | /**
131 | * {@inheritdoc}
132 | */
133 | public function offsetSet(mixed $offset, mixed $value): void
134 | {
135 | throw new BadMethodCallException('Cannot set resource attributes.');
136 | }
137 |
138 | /**
139 | * {@inheritdoc}
140 | */
141 | public function offsetUnset(mixed $offset): void
142 | {
143 | throw new BadMethodCallException('Cannot unset resource attributes.');
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/src/Service/ApiKey.php:
--------------------------------------------------------------------------------
1 | transporter->request($payload);
19 |
20 | return $this->createResource('api-keys', $result);
21 | }
22 |
23 | /**
24 | * List all API keys.
25 | *
26 | * @return \Resend\Collection<\Resend\ApiKey>
27 | *
28 | * @see https://resend.com/docs/api-reference/api-keys/list-api-keys
29 | */
30 | public function list(): \Resend\Collection
31 | {
32 | $payload = Payload::list('api-keys');
33 |
34 | $result = $this->transporter->request($payload);
35 |
36 | return $this->createResource('api-keys', $result);
37 | }
38 |
39 | /**
40 | * Remove an API key with the given ID.
41 | *
42 | * @see https://resend.com/docs/api-reference/api-keys/delete-api-key#path-parameters
43 | */
44 | public function remove(string $id): \Resend\ApiKey
45 | {
46 | $payload = Payload::delete('api-keys', $id);
47 |
48 | $result = $this->transporter->request($payload);
49 |
50 | return $this->createResource('api-keys', $result);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Service/Audience.php:
--------------------------------------------------------------------------------
1 | transporter->request($payload);
19 |
20 | return $this->createResource('audiences', $result);
21 | }
22 |
23 | /**
24 | * Add a new audience.
25 | *
26 | * @see https://resend.com/docs/api-reference/audiences/create-audience
27 | */
28 | public function create(array $parameters): \Resend\Audience
29 | {
30 | $payload = Payload::create('audiences', $parameters);
31 |
32 | $result = $this->transporter->request($payload);
33 |
34 | return $this->createResource('audiences', $result);
35 | }
36 |
37 | /**
38 | * List all audiences.
39 | *
40 | * @return \Resend\Collection<\Resend\Audience>
41 | *
42 | * @see https://resend.com/docs/api-reference/audiences/list-audiences
43 | */
44 | public function list(): \Resend\Collection
45 | {
46 | $payload = Payload::list('audiences');
47 |
48 | $result = $this->transporter->request($payload);
49 |
50 | return $this->createResource('audiences', $result);
51 | }
52 |
53 | /**
54 | * Remove a audience with the given ID.
55 | *
56 | * @see https://resend.com/docs/api-reference/audiences/delete-audience
57 | */
58 | public function remove(string $id): \Resend\Audience
59 | {
60 | $payload = Payload::delete('audiences', $id);
61 |
62 | $result = $this->transporter->request($payload);
63 |
64 | return $this->createResource('audiences', $result);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Service/Batch.php:
--------------------------------------------------------------------------------
1 |
13 | *
14 | * @see https://resend.com/docs/api-reference/emails/send-batch-emails#body-parameters
15 | */
16 | public function create(array $parameters, array $options = []): \Resend\Collection
17 | {
18 | $payload = Payload::create('emails/batch', $parameters, $options);
19 |
20 | $result = $this->transporter->request($payload);
21 |
22 | return $this->createResource('emails', $result);
23 | }
24 |
25 | /**
26 | * Send a batch of emails with the given parameters.
27 | *
28 | * @return \Resend\Collection<\Resend\Email>
29 | *
30 | * @see https://resend.com/docs/api-reference/emails/send-batch-emails#body-parameters
31 | */
32 | public function send(array $parameters, array $options = []): \Resend\Collection
33 | {
34 | return $this->create($parameters, $options);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Service/Broadcast.php:
--------------------------------------------------------------------------------
1 | transporter->request($payload);
19 |
20 | return $this->createResource('broadcasts', $result);
21 | }
22 |
23 | /**
24 | * Create a new broadcast to send to your audience.
25 | *
26 | * @see https://resend.com/docs/api-reference/broadcasts/create-broadcast
27 | */
28 | public function create(array $parameters): \Resend\Broadcast
29 | {
30 | $payload = Payload::create('broadcasts', $parameters);
31 |
32 | $result = $this->transporter->request($payload);
33 |
34 | return $this->createResource('broadcasts', $result);
35 | }
36 |
37 | /**
38 | * List all domains.
39 | *
40 | * @return \Resend\Collection<\Resend\Broadcast>
41 | *
42 | * @see https://resend.com/docs/api-reference/broadcasts/list-broadcasts
43 | */
44 | public function list(): \Resend\Collection
45 | {
46 | $payload = Payload::list('broadcasts');
47 |
48 | $result = $this->transporter->request($payload);
49 |
50 | return $this->createResource('broadcasts', $result);
51 | }
52 |
53 | /**
54 | * Update a broadcast to send to your audience.
55 | *
56 | * @see https://resend.com/docs/api-reference/broadcasts/update-broadcast
57 | */
58 | public function update(string $id, array $parameters): \Resend\Broadcast
59 | {
60 | $payload = Payload::update('broadcasts', $id, $parameters);
61 |
62 | $result = $this->transporter->request($payload);
63 |
64 | return $this->createResource('broadcasts', $result);
65 | }
66 |
67 | /**
68 | * Start sending broadcasts to your audience.
69 | *
70 | * @see https://resend.com/docs/api-reference/broadcasts/send-broadcast
71 | */
72 | public function send(string $broadcastId, array $parameters): \Resend\Broadcast
73 | {
74 | $payload = Payload::create("broadcasts/{$broadcastId}/send", $parameters);
75 |
76 | $result = $this->transporter->request($payload);
77 |
78 | return $this->createResource('broadcasts', $result);
79 | }
80 |
81 | /**
82 | * Remove an existing broadcast.
83 | *
84 | * @see https://resend.com/docs/api-reference/broadcasts/delete-broadcast
85 | */
86 | public function remove(string $id): \Resend\Broadcast
87 | {
88 | $payload = Payload::delete('broadcasts', $id);
89 |
90 | $result = $this->transporter->request($payload);
91 |
92 | return $this->createResource('broadcasts', $result);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/Service/Contact.php:
--------------------------------------------------------------------------------
1 | transporter->request($payload);
26 |
27 | return $this->createResource('contacts', $result);
28 | }
29 |
30 | /**
31 | * Add a contact to an audience.
32 | *
33 | * @see https://resend.com/docs/api-reference/contacts/create-contact
34 | */
35 | public function create(string $audienceId, array $parameters): \Resend\Contact
36 | {
37 | $payload = Payload::create("audiences/$audienceId/contacts", $parameters);
38 |
39 | $result = $this->transporter->request($payload);
40 |
41 | return $this->createResource('contacts', $result);
42 | }
43 |
44 | /**
45 | * List all contacts from an audience.
46 | *
47 | * @return \Resend\Collection<\Resend\Contact>
48 | *
49 | * @see https://resend.com/docs/api-reference/contacts/list-contacts
50 | */
51 | public function list(string $audienceId): \Resend\Collection
52 | {
53 | $payload = Payload::list("audiences/$audienceId/contacts");
54 |
55 | $result = $this->transporter->request($payload);
56 |
57 | return $this->createResource('contacts', $result);
58 | }
59 |
60 | /**
61 | * Update a contact in an audience.
62 | *
63 | * @see https://resend.com/docs/api-reference/contacts/update-contacts
64 | */
65 | public function update(string $audienceId, string $id, array $parameters): \Resend\Contact
66 | {
67 | $payload = Payload::update("audiences/$audienceId/contacts", $id, $parameters);
68 |
69 | $result = $this->transporter->request($payload);
70 |
71 | return $this->createResource('contacts', $result);
72 | }
73 |
74 | /**
75 | * Remove a contact from an audience.
76 | *
77 | * @see https://resend.com/docs/api-reference/contacts/delete-contact
78 | */
79 | public function remove(string $audienceId, string $id): \Resend\Contact
80 | {
81 | $payload = Payload::delete("audiences/$audienceId/contacts", $id);
82 |
83 | $result = $this->transporter->request($payload);
84 |
85 | return $this->createResource('contacts', $result);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/Service/Domain.php:
--------------------------------------------------------------------------------
1 | transporter->request($payload);
19 |
20 | return $this->createResource('domains', $result);
21 | }
22 |
23 | /**
24 | * Add a new domain.
25 | *
26 | * @see https://resend.com/docs/api-reference/domains/create-domain#body-parameters
27 | */
28 | public function create(array $parameters): \Resend\Domain
29 | {
30 | $payload = Payload::create('domains', $parameters);
31 |
32 | $result = $this->transporter->request($payload);
33 |
34 | return $this->createResource('domains', $result);
35 | }
36 |
37 | /**
38 | * List all domains.
39 | *
40 | * @return \Resend\Collection<\Resend\Domain>
41 | *
42 | * @see https://resend.com/docs/api-reference/domains/list-domains
43 | */
44 | public function list(): \Resend\Collection
45 | {
46 | $payload = Payload::list('domains');
47 |
48 | $result = $this->transporter->request($payload);
49 |
50 | return $this->createResource('domains', $result);
51 | }
52 |
53 | /**
54 | * Update a domain with the given ID.
55 | *
56 | * @see https://resend.com/docs/api-reference/domains/update-domain
57 | */
58 | public function update(string $id, array $parameters): \Resend\Domain
59 | {
60 | $payload = Payload::update('domains', $id, $parameters);
61 |
62 | $result = $this->transporter->request($payload);
63 |
64 | return $this->createResource('domains', $result);
65 | }
66 |
67 | /**
68 | * Remove a domain with the given ID.
69 | *
70 | * @see https://resend.com/docs/api-reference/domains/delete-domain#path-parameters
71 | */
72 | public function remove(string $id): \Resend\Domain
73 | {
74 | $payload = Payload::delete('domains', $id);
75 |
76 | $result = $this->transporter->request($payload);
77 |
78 | return $this->createResource('domains', $result);
79 | }
80 |
81 | /**
82 | * Verify a domain with the given ID.
83 | *
84 | * @see https://resend.com/docs/api-reference/domains/verify-domain#path-parameters
85 | */
86 | public function verify(string $id): \Resend\Domain
87 | {
88 | $payload = Payload::verify('domains', $id);
89 |
90 | $result = $this->transporter->request($payload);
91 |
92 | return $this->createResource('domains', $result);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/Service/Email.php:
--------------------------------------------------------------------------------
1 | transporter->request($payload);
19 |
20 | return $this->createResource('emails', $result);
21 | }
22 |
23 | /**
24 | * Send an email with the given parameters.
25 | *
26 | * @see https://resend.com/docs/api-reference/emails/send-email#body-parameters
27 | */
28 | public function create(array $parameters, array $options = []): \Resend\Email
29 | {
30 | $payload = Payload::create('emails', $parameters, $options);
31 |
32 | $result = $this->transporter->request($payload);
33 |
34 | return $this->createResource('emails', $result);
35 | }
36 |
37 | /**
38 | * Send an email with the given parameters.
39 | *
40 | * @see https://resend.com/docs/api-reference/emails/send-email#body-parameters
41 | */
42 | public function send(array $parameters, array $options = []): \Resend\Email
43 | {
44 | return $this->create($parameters, $options);
45 | }
46 |
47 | /**
48 | * Update a scheduled email with the given ID.
49 | *
50 | * @see https://resend.com/docs/api-reference/emails/update-email
51 | */
52 | public function update(string $id, array $parameters): \Resend\Email
53 | {
54 | $payload = Payload::update('emails', $id, $parameters);
55 |
56 | $result = $this->transporter->request($payload);
57 |
58 | return $this->createResource('emails', $result);
59 | }
60 |
61 | /**
62 | * Cancel a scheduled email with the given ID.
63 | *
64 | * @see https://resend.com/docs/api-reference/emails/cancel-email
65 | */
66 | public function cancel(string $id): \Resend\Email
67 | {
68 | $payload = Payload::cancel('emails', $id);
69 |
70 | $result = $this->transporter->request($payload);
71 |
72 | return $this->createResource('emails', $result);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Service/Service.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | protected $mapping = [
21 | 'api-keys' => ApiKey::class,
22 | 'audiences' => Audience::class,
23 | 'broadcasts' => Broadcast::class,
24 | 'contacts' => Contact::class,
25 | 'domains' => Domain::class,
26 | 'emails' => Email::class,
27 | ];
28 |
29 | /**
30 | * Create a transportable instance with the given transporter.
31 | */
32 | public function __construct(
33 | protected readonly Transporter $transporter
34 | ) {
35 | //
36 | }
37 |
38 | /**
39 | * Create a new resource for the given with the given attributes.
40 | */
41 | protected function createResource(string $resourceType, array $attributes)
42 | {
43 | $class = isset($this->mapping[$resourceType]) ? $this->mapping[$resourceType] : Resource::class;
44 |
45 | if (isset($attributes['data']) && is_array($attributes['data'])) {
46 | foreach ($attributes['data'] as $key => $value) {
47 | $attributes['data'][$key] = $class::from($value);
48 | }
49 |
50 | return Collection::from($attributes);
51 | } else {
52 | return $class::from($attributes);
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Service/ServiceFactory.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | private static array $classMap = [
15 | 'apiKeys' => ApiKey::class,
16 | 'audiences' => Audience::class,
17 | 'batch' => Batch::class,
18 | 'broadcasts' => Broadcast::class,
19 | 'contacts' => Contact::class,
20 | 'domains' => Domain::class,
21 | 'emails' => Email::class,
22 | ];
23 |
24 | /**
25 | * A list of available services.
26 | *
27 | * @var array
28 | */
29 | private array $services = [];
30 |
31 | /**
32 | * Create a new Service Factory instance.
33 | */
34 | public function __construct(
35 | private readonly Transporter $transporter
36 | ) {
37 | //
38 | }
39 |
40 | /**
41 | * Get the given service by name.
42 | */
43 | public function getService(string $name)
44 | {
45 | $serviceClass = array_key_exists($name, self::$classMap) ? self::$classMap[$name] : null;
46 |
47 | if (! $serviceClass) {
48 | return null;
49 | }
50 |
51 | if (! array_key_exists($name, $this->services)) {
52 | $this->services[$name] = new $serviceClass($this->transporter);
53 | }
54 |
55 | return $this->services[$name];
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Transporters/HttpTransporter.php:
--------------------------------------------------------------------------------
1 |
36 | *
37 | * @throws ErrorException|TransporterException|UnserializableResponse
38 | */
39 | public function request(Payload $payload): array
40 | {
41 | $request = $payload->toRequest($this->baseUri, $this->headers);
42 |
43 | $response = $this->sendRequest(fn () => $this->client->sendRequest($request));
44 |
45 | $contents = $response->getBody()->getContents();
46 |
47 | $this->throwIfJsonError($response, $contents);
48 |
49 | try {
50 | $data = json_decode($contents, true, 512, JSON_THROW_ON_ERROR);
51 | } catch (JsonException $jsonException) {
52 | throw new UnserializableResponse($jsonException);
53 | }
54 |
55 | return $data;
56 | }
57 |
58 | /**
59 | * Send the given request callable.
60 | */
61 | private function sendRequest(Closure $callable): ResponseInterface
62 | {
63 | try {
64 | return $callable();
65 | } catch (ClientExceptionInterface $clientException) {
66 | if ($clientException instanceof ClientException) {
67 | $this->throwIfJsonError($clientException->getResponse(), $clientException->getResponse()->getBody()->getContents());
68 | }
69 |
70 | throw new TransporterException($clientException);
71 | }
72 | }
73 |
74 | /**
75 | * Throw an exception if there is a JSON error.
76 | */
77 | protected function throwIfJsonError(ResponseInterface $response, string $contents): void
78 | {
79 | if ($response->getStatusCode() < 400) {
80 | return;
81 | }
82 |
83 | // Only handle JSON content types...
84 | if (! str_contains($response->getHeaderLine('Content-Type'), 'application/json')) {
85 | return;
86 | }
87 |
88 | try {
89 | $response = json_decode($contents, true, 512, JSON_THROW_ON_ERROR);
90 |
91 | if (
92 | isset($response['error']) ||
93 | $this->isResendError($response['name'])
94 | ) {
95 | throw new ErrorException($response['error'] ?? $response);
96 | }
97 | } catch (JsonException $jsonException) {
98 | throw new UnserializableResponse($jsonException);
99 | }
100 | }
101 |
102 | /**
103 | * Determine if the given error name is a Resend error.
104 | */
105 | protected function isResendError(string $errorName): bool
106 | {
107 | $errors = [
108 | 'application_error',
109 | 'concurrent_idempotent_requests',
110 | 'daily_quota_exceeded',
111 | 'invalid_attachment',
112 | 'invalid_idempotency_key',
113 | 'invalid_idempotent_request',
114 | 'missing_api_key',
115 | 'missing_required_field',
116 | 'not_found',
117 | 'rate_limit_exceeded',
118 | 'restricted_api_key',
119 | 'security_error',
120 | 'validation_error',
121 | ];
122 |
123 | return in_array($errorName, $errors);
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/ValueObjects/ApiKey.php:
--------------------------------------------------------------------------------
1 | apiKey;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/ValueObjects/ResourceUri.php:
--------------------------------------------------------------------------------
1 | uri;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/ValueObjects/Transporter/BaseUri.php:
--------------------------------------------------------------------------------
1 | baseUri, $protocol)) {
33 | return str_ends_with($this->baseUri, '/')
34 | ? "{$this->baseUri}"
35 | : "{$this->baseUri}/";
36 | }
37 | }
38 |
39 | return "https://{$this->baseUri}/";
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/ValueObjects/Transporter/Headers.php:
--------------------------------------------------------------------------------
1 | $headers
14 | */
15 | public function __construct(
16 | private readonly array $headers
17 | ) {
18 | //
19 | }
20 |
21 | /**
22 | * Create a new Headers value object with the given API key.
23 | */
24 | public static function withAuthorization(ApiKey $apiKey): self
25 | {
26 | return new self([
27 | 'Authorization' => "Bearer {$apiKey->toString()}",
28 | ]);
29 | }
30 |
31 | /**
32 | * Create a new Headers value object with the given user agent and existing headers.
33 | */
34 | public function withUserAgent(string $name, string $version): self
35 | {
36 | return new self([
37 | ...$this->headers,
38 | 'User-Agent' => $name . '/' . $version,
39 | ]);
40 | }
41 |
42 | /**
43 | * Create a new Headers value object with the given content type and existing headers.
44 | */
45 | public function withContentType(ContentType $contentType, string $suffix = ''): self
46 | {
47 | return new self([
48 | ...$this->headers,
49 | 'Content-Type' => $contentType->value . $suffix,
50 | ]);
51 | }
52 |
53 | /**
54 | * Create a new Headers value object with the given idempotency key and existing headers.
55 | */
56 | public function withIdempotencyKey(string $key): self
57 | {
58 | return new self([
59 | ...$this->headers,
60 | 'Idempotency-Key' => $key,
61 | ]);
62 | }
63 |
64 | /**
65 | * Return the headers as an array.
66 | */
67 | public function toArray(): array
68 | {
69 | return $this->headers;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/ValueObjects/Transporter/Payload.php:
--------------------------------------------------------------------------------
1 | toString() . $this->uri->toString();
124 |
125 | $headers = $headers->withUserAgent('resend-php', Resend::VERSION)
126 | ->withContentType($this->contentType);
127 |
128 | if ($this->idempotencyKey) {
129 | $headers = $headers->withIdempotencyKey($this->idempotencyKey);
130 | }
131 |
132 | if ($this->method === Method::POST || $this->method === Method::PATCH || $this->method === Method::PUT) {
133 | $body = json_encode(
134 | $this->parameters === [] || ! array_is_list($this->parameters) ? (object) $this->parameters : $this->parameters,
135 | JSON_THROW_ON_ERROR
136 | );
137 | }
138 |
139 | return new Request($this->method->value, $uri, $headers->toArray(), $body);
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/WebhookSignature.php:
--------------------------------------------------------------------------------
1 | ($now + $tolerance)) {
91 | throw new WebhookSignatureVerificationException('Message timestamp too new');
92 | }
93 |
94 | return $timestamp;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------