Skip to content

Commit 5615088

Browse files
committed
wip
1 parent 082123d commit 5615088

118 files changed

Lines changed: 2329 additions & 377 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ Initialize the Lettermint client with your Sending API token:
2929
$lettermint = new Lettermint\Lettermint('your-api-token');
3030
```
3131

32-
For new integrations, prefer the explicit Sending and Team clients:
32+
For new integrations, prefer explicit clients for the two API surfaces:
3333

3434
```php
35-
$sending = Lettermint\Lettermint::sending(getenv('LETTERMINT_SENDING_TOKEN'));
36-
$team = Lettermint\Lettermint::team(getenv('LETTERMINT_TEAM_TOKEN'));
35+
$email = Lettermint\Lettermint::email(getenv('LETTERMINT_SENDING_TOKEN'));
36+
$api = Lettermint\Lettermint::api(getenv('LETTERMINT_API_TOKEN'));
3737
```
3838

39-
Sending API tokens are project-specific and authenticate with the `x-lettermint-token` header. Team API tokens are team-scoped and authenticate with `Authorization: Bearer ...`. Keep these tokens separate and never reuse a Team token for sending-only workloads.
39+
Sending API tokens are project-specific and authenticate with the `x-lettermint-token` header. API tokens are team-scoped and authenticate with `Authorization: Bearer ...`. Keep these tokens separate and never reuse an API token for sending-only workloads.
4040

4141
### Sending Emails
4242

@@ -75,7 +75,7 @@ $lettermint->email
7575
You can also send with an array payload:
7676

7777
```php
78-
$response = $sending->email->send([
78+
$response = $email->send([
7979
'from' => 'sender@example.com',
8080
'to' => ['recipient@example.com'],
8181
'subject' => 'Hello from Lettermint!',
@@ -86,7 +86,7 @@ $response = $sending->email->send([
8686
### Batch Sending
8787

8888
```php
89-
$response = $sending->email->sendBatch([
89+
$response = $email->sendBatch([
9090
[
9191
'from' => 'sender@example.com',
9292
'to' => ['recipient@example.com'],
@@ -135,46 +135,46 @@ same request with the same idempotency key, the API will return the same respons
135135

136136
For more information, refer to the [documentation](https://docs.lettermint.co/platform/emails/idempotency).
137137

138-
### Team API
138+
### API Client
139139

140-
Use the Team API client for team-scoped resources such as projects, domains, routes, suppressions, stats, messages, and webhooks:
140+
Use the API client for team-scoped resources such as projects, domains, routes, suppressions, stats, messages, and webhooks:
141141

142142
```php
143-
$team = Lettermint\Lettermint::team(getenv('LETTERMINT_TEAM_TOKEN'));
143+
$api = Lettermint\Lettermint::api(getenv('LETTERMINT_API_TOKEN'));
144144

145-
$projects = $team->projects->list(['filter[search]' => 'production']);
145+
$projects = $api->projects->list(['filter[search]' => 'production']);
146146

147-
$project = $team->projects->create([
147+
$project = $api->projects->create([
148148
'name' => 'Production',
149149
'smtp_enabled' => false,
150150
]);
151151

152-
$team->domains->verifyDnsRecords('domain-id');
152+
$api->domains->verifyDnsRecords('domain-id');
153153

154-
$stats = $team->stats->retrieve([
154+
$stats = $api->stats->retrieve([
155155
'from' => '2026-05-01',
156156
'to' => '2026-05-09',
157157
]);
158158

159-
$team->suppressions->create([
159+
$api->suppressions->create([
160160
'email' => 'user@example.com',
161161
'reason' => 'manual',
162162
'scope' => 'team',
163163
]);
164164

165-
$team->webhooks->create([
165+
$api->webhooks->create([
166166
'route_id' => 'route-id',
167167
'name' => 'Production webhook',
168168
'url' => 'https://example.com/lettermint/webhook',
169169
'events' => ['message.sent', 'message.delivered'],
170170
]);
171171
```
172172

173-
Both clients support `ping()`:
173+
Both API surfaces support `ping()`:
174174

175175
```php
176-
$sending->ping();
177-
$team->ping();
176+
$email->ping();
177+
$api->ping();
178178
```
179179

180180
## Testing
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
* @property-read TeamEndpoint $team Access team operations.
2323
* @property-read WebhooksEndpoint $webhooks Access webhook operations.
2424
*/
25-
class TeamClient
25+
class ApiClient
2626
{
2727
private HttpClient $httpClient;
2828

src/Client/SendingClient.php

Lines changed: 0 additions & 55 deletions
This file was deleted.

src/Endpoints/DomainsEndpoint.php

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,64 +2,60 @@
22

33
namespace Lettermint\Endpoints;
44

5+
use Lettermint\Responses\DeleteDomainResponse;
6+
use Lettermint\Responses\DomainListResponse;
7+
use Lettermint\Responses\DomainResponse;
8+
use Lettermint\Responses\UpdateDomainProjectsResponse;
9+
use Lettermint\Responses\VerifyDnsRecordResponse;
10+
use Lettermint\Responses\VerifyDnsRecordsResponse;
11+
512
/**
6-
* @phpstan-import-type CursorPage from \Lettermint\Types\ApiTypes
7-
* @phpstan-import-type ApiObject from \Lettermint\Types\ApiTypes
813
* @phpstan-import-type StoreDomainData from \Lettermint\Types\ApiTypes
914
* @phpstan-import-type UpdateDomainProjectsData from \Lettermint\Types\ApiTypes
1015
*/
1116
class DomainsEndpoint extends Endpoint
1217
{
13-
/** @phpstan-return CursorPage */
14-
public function list(array $query = []): array
18+
public function list(array $query = []): DomainListResponse
1519
{
16-
return $this->getArray($this->path('/domains'), $query);
20+
return $this->hydrate(DomainListResponse::class, $this->getArray($this->path('/domains'), $query));
1721
}
1822

1923
/**
2024
* @phpstan-param StoreDomainData $data
21-
*
22-
* @phpstan-return ApiObject
2325
*/
24-
public function create(array $data): array
26+
public function create(array $data): DomainResponse
2527
{
26-
return $this->postArray($this->path('/domains'), $data, []);
28+
return $this->hydrate(DomainResponse::class, $this->postArray($this->path('/domains'), $data, []));
2729
}
2830

29-
/** @phpstan-return ApiObject */
30-
public function retrieve(string $domainId, array $query = []): array
31+
public function retrieve(string $domainId, array $query = []): DomainResponse
3132
{
32-
return $this->getArray($this->path('/domains/{domainId}', ['domainId' => $domainId]), $query);
33+
return $this->hydrate(DomainResponse::class, $this->getArray($this->path('/domains/{domainId}', ['domainId' => $domainId]), $query));
3334
}
3435

35-
/** @phpstan-return ApiObject */
36-
public function delete(string $domainId): array
36+
public function delete(string $domainId): DeleteDomainResponse
3737
{
38-
return $this->deleteArray($this->path('/domains/{domainId}', ['domainId' => $domainId]), []);
38+
return $this->hydrate(DeleteDomainResponse::class, $this->deleteArray($this->path('/domains/{domainId}', ['domainId' => $domainId]), []));
3939
}
4040

41-
/** @phpstan-return ApiObject */
42-
public function verifyDnsRecords(string $domainId): array
41+
public function verifyDnsRecords(string $domainId): VerifyDnsRecordsResponse
4342
{
44-
return $this->postArray($this->path('/domains/{domainId}/dns-records/verify', ['domainId' => $domainId]), [], []);
43+
return $this->hydrate(VerifyDnsRecordsResponse::class, $this->postArray($this->path('/domains/{domainId}/dns-records/verify', ['domainId' => $domainId]), [], []));
4544
}
4645

47-
/** @phpstan-return ApiObject */
48-
public function verifyDnsRecord(string $domainId, string $recordId): array
46+
public function verifyDnsRecord(string $domainId, string $recordId): VerifyDnsRecordResponse
4947
{
50-
return $this->postArray($this->path('/domains/{domainId}/dns-records/{recordId}/verify', [
48+
return $this->hydrate(VerifyDnsRecordResponse::class, $this->postArray($this->path('/domains/{domainId}/dns-records/{recordId}/verify', [
5149
'domainId' => $domainId,
5250
'recordId' => $recordId,
53-
]), [], []);
51+
]), [], []));
5452
}
5553

5654
/**
5755
* @phpstan-param UpdateDomainProjectsData $data
58-
*
59-
* @phpstan-return ApiObject
6056
*/
61-
public function updateProjects(string $domainId, array $data): array
57+
public function updateProjects(string $domainId, array $data): UpdateDomainProjectsResponse
6258
{
63-
return $this->putArray($this->path('/domains/{domainId}/projects', ['domainId' => $domainId]), $data, []);
59+
return $this->hydrate(UpdateDomainProjectsResponse::class, $this->putArray($this->path('/domains/{domainId}/projects', ['domainId' => $domainId]), $data, []));
6460
}
6561
}

src/Endpoints/EmailEndpoint.php

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
namespace Lettermint\Endpoints;
44

5+
use Lettermint\Responses\SendBatchMailResponse;
6+
use Lettermint\Responses\SendMailResponse;
7+
58
/**
69
* @phpstan-import-type SendMailRequest from \Lettermint\Types\ApiTypes
710
* @phpstan-import-type SendBatchMailRequest from \Lettermint\Types\ApiTypes
8-
* @phpstan-import-type SendMailResponse from \Lettermint\Types\ApiTypes
9-
* @phpstan-import-type SendBatchMailResponse from \Lettermint\Types\ApiTypes
1011
*
1112
* @phpstan-type AttachmentPayload array{
1213
* filename: string,
@@ -34,10 +35,6 @@
3435
* settings?: EmailSettings|null,
3536
* headers?: array<string, string>
3637
* }
37-
* @phpstan-type SendResponse array{
38-
* message_id: string,
39-
* status: string
40-
* }
4138
*/
4239
class EmailEndpoint extends Endpoint
4340
{
@@ -272,25 +269,21 @@ public function ping(): int
272269
*
273270
* @phpstan-param SendBatchMailRequest $messages
274271
*
275-
* @phpstan-return SendBatchMailResponse
276-
*
277272
* @throws \Exception On HTTP or API failure.
278273
*/
279-
public function sendBatch(array $messages): array
274+
public function sendBatch(array $messages): SendBatchMailResponse
280275
{
281-
return $this->postArray('/v1/send/batch', $messages, []);
276+
return $this->hydrateList(SendBatchMailResponse::class, $this->postArray('/v1/send/batch', $messages, []));
282277
}
283278

284279
/**
285280
* Send the composed email using the current payload.
286281
*
287282
* @phpstan-param SendMailRequest|null $payload
288283
*
289-
* @phpstan-return SendMailResponse
290-
*
291284
* @throws \Exception On HTTP or API failure.
292285
*/
293-
public function send(?array $payload = null): array
286+
public function send(?array $payload = null): SendMailResponse
294287
{
295288
$headers = [];
296289

@@ -303,6 +296,6 @@ public function send(?array $payload = null): array
303296
$this->payload = [];
304297
$this->idempotencyKey = null;
305298

306-
return $result;
299+
return $this->hydrate(SendMailResponse::class, $result);
307300
}
308301
}

src/Endpoints/Endpoint.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Lettermint\Endpoints;
44

55
use Lettermint\Client\HttpClient;
6+
use Lettermint\Resource;
67

78
abstract class Endpoint
89
{
@@ -102,4 +103,31 @@ private function expectArray(mixed $response): array
102103

103104
return $response;
104105
}
106+
107+
/**
108+
* @template T of Resource
109+
*
110+
* @param class-string<T> $class
111+
* @param array<array-key, mixed> $response
112+
* @return T
113+
*/
114+
protected function hydrate(string $class, array $response): Resource
115+
{
116+
/** @var T $resource */
117+
$resource = new $class($response);
118+
119+
return $resource;
120+
}
121+
122+
/**
123+
* @template T of Resource
124+
*
125+
* @param class-string<T> $class
126+
* @param array<array-key, mixed> $response
127+
* @return T
128+
*/
129+
protected function hydrateList(string $class, array $response): Resource
130+
{
131+
return $this->hydrate($class, array_is_list($response) ? ['data' => $response] : $response);
132+
}
105133
}

src/Endpoints/MessagesEndpoint.php

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,25 @@
22

33
namespace Lettermint\Endpoints;
44

5-
/**
6-
* @phpstan-import-type CursorPage from \Lettermint\Types\ApiTypes
7-
* @phpstan-import-type ApiObject from \Lettermint\Types\ApiTypes
8-
*/
5+
use Lettermint\Responses\MessageEventsResponse;
6+
use Lettermint\Responses\MessageListResponse;
7+
use Lettermint\Responses\MessageResponse;
8+
99
class MessagesEndpoint extends Endpoint
1010
{
11-
/** @phpstan-return CursorPage|list<ApiObject> */
12-
public function list(array $query = []): array
11+
public function list(array $query = []): MessageListResponse
1312
{
14-
return $this->getArray($this->path('/messages'), $query);
13+
return $this->hydrateList(MessageListResponse::class, $this->getArray($this->path('/messages'), $query));
1514
}
1615

17-
/** @phpstan-return ApiObject */
18-
public function retrieve(string $messageId): array
16+
public function retrieve(string $messageId): MessageResponse
1917
{
20-
return $this->getArray($this->path('/messages/{messageId}', ['messageId' => $messageId]), []);
18+
return $this->hydrate(MessageResponse::class, $this->getArray($this->path('/messages/{messageId}', ['messageId' => $messageId]), []));
2119
}
2220

23-
/** @phpstan-return ApiObject */
24-
public function events(string $messageId, array $query = []): array
21+
public function events(string $messageId, array $query = []): MessageEventsResponse
2522
{
26-
return $this->getArray($this->path('/messages/{messageId}/events', ['messageId' => $messageId]), $query);
23+
return $this->hydrate(MessageEventsResponse::class, $this->getArray($this->path('/messages/{messageId}/events', ['messageId' => $messageId]), $query));
2724
}
2825

2926
public function source(string $messageId): string

0 commit comments

Comments
 (0)