Skip to content

Commit 0d48e65

Browse files
authored
feat: add Team API endpoints (#22)
* feat: add Team API endpoints Added Team API endpoints for Domains, Messages, Projects, Routes, and core authentication strategies. * wip * refactor: update ping response handling to return trimmed string instead of integer Adjusted `ping` methods in `EmailEndpoint` and `ApiClient` to return a trimmed string response. Updated tests accordingly to validate the new behavior. * docs: add upgrade guide for migrating from v1 to v2 Added `UPGRADE.md` with detailed instructions for upgrading the PHP SDK from v1 to v2. Updated `README.md` to reference the new upgrade guide. * fix
1 parent 07181af commit 0d48e65

127 files changed

Lines changed: 4065 additions & 51 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.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,5 @@ testbench.yaml
3333
composer.lock
3434
openapi.json
3535
CLAUDE.md
36+
AGENTS.md
37+
.DS_Store

README.md

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,21 @@ composer require lettermint/lettermint-php
2323

2424
## Usage
2525

26-
Initialize the Lettermint client with your API token:
26+
Initialize the Lettermint client with your Sending API token:
2727

2828
```php
2929
$lettermint = new Lettermint\Lettermint('your-api-token');
3030
```
3131

32+
For new integrations, prefer explicit clients for the two API surfaces:
33+
34+
```php
35+
$email = Lettermint\Lettermint::email(getenv('LETTERMINT_SENDING_TOKEN'));
36+
$api = Lettermint\Lettermint::api(getenv('LETTERMINT_API_TOKEN'));
37+
```
38+
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.
40+
3241
### Sending Emails
3342

3443
The SDK provides a fluent interface for composing and sending emails:
@@ -63,6 +72,36 @@ $lettermint->email
6372
->send();
6473
```
6574

75+
You can also send with an array payload:
76+
77+
```php
78+
$response = $email->send([
79+
'from' => 'sender@example.com',
80+
'to' => ['recipient@example.com'],
81+
'subject' => 'Hello from Lettermint!',
82+
'text' => 'Hello! This is a test email.',
83+
]);
84+
```
85+
86+
### Batch Sending
87+
88+
```php
89+
$response = $email->sendBatch([
90+
[
91+
'from' => 'sender@example.com',
92+
'to' => ['recipient@example.com'],
93+
'subject' => 'First email',
94+
'text' => 'Hello!',
95+
],
96+
[
97+
'from' => 'sender@example.com',
98+
'to' => ['another@example.com'],
99+
'subject' => 'Second email',
100+
'text' => 'Hello again!',
101+
],
102+
]);
103+
```
104+
66105
#### Inline Attachments
67106

68107
You can embed images and other content in your HTML emails using content IDs:
@@ -96,6 +135,48 @@ same request with the same idempotency key, the API will return the same respons
96135

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

138+
### API Client
139+
140+
Use the API client for team-scoped resources such as projects, domains, routes, suppressions, stats, messages, and webhooks:
141+
142+
```php
143+
$api = Lettermint\Lettermint::api(getenv('LETTERMINT_API_TOKEN'));
144+
145+
$projects = $api->projects->list(['filter[search]' => 'production']);
146+
147+
$project = $api->projects->create([
148+
'name' => 'Production',
149+
'smtp_enabled' => false,
150+
]);
151+
152+
$api->domains->verifyDnsRecords('domain-id');
153+
154+
$stats = $api->stats->retrieve([
155+
'from' => '2026-05-01',
156+
'to' => '2026-05-09',
157+
]);
158+
159+
$api->suppressions->create([
160+
'email' => 'user@example.com',
161+
'reason' => 'manual',
162+
'scope' => 'team',
163+
]);
164+
165+
$api->webhooks->create([
166+
'route_id' => 'route-id',
167+
'name' => 'Production webhook',
168+
'url' => 'https://example.com/lettermint/webhook',
169+
'events' => ['message.sent', 'message.delivered'],
170+
]);
171+
```
172+
173+
Both API surfaces support `ping()`:
174+
175+
```php
176+
$email->ping();
177+
$api->ping();
178+
```
179+
99180
## Testing
100181

101182
```bash
@@ -106,6 +187,10 @@ composer test
106187

107188
Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
108189

190+
## Upgrading
191+
192+
Please see [UPGRADE.md](UPGRADE.md) for guidance on upgrading from v1 to v2.
193+
109194
## Contributing
110195

111196
Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

UPGRADE.md

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# Upgrade Guide
2+
3+
## Upgrading from v1 to v2
4+
5+
Version 2 changes the PHP SDK response model for the sending API. The latest released v1 SDK only exposed email sending, so this guide focuses on migrating existing sending integrations.
6+
7+
### 1. Update Composer
8+
9+
```bash
10+
composer require lettermint/lettermint-php:^2.0
11+
```
12+
13+
### 2. Prefer the new email client entry point
14+
15+
The v1 constructor-based style still maps to the email endpoint, but v2 introduces a clearer sending entry point:
16+
17+
```php
18+
$email = Lettermint\Lettermint::email($sendingToken);
19+
```
20+
21+
Before:
22+
23+
```php
24+
$lettermint = new Lettermint\Lettermint($sendingToken);
25+
26+
$response = $lettermint->email
27+
->from('sender@example.com')
28+
->to('recipient@example.com')
29+
->subject('Hello')
30+
->send();
31+
```
32+
33+
After:
34+
35+
```php
36+
$email = Lettermint\Lettermint::email($sendingToken);
37+
38+
$response = $email
39+
->from('sender@example.com')
40+
->to('recipient@example.com')
41+
->subject('Hello')
42+
->send();
43+
```
44+
45+
Direct payload sending changes the same way:
46+
47+
```php
48+
$response = $email->send([
49+
'from' => 'sender@example.com',
50+
'to' => ['recipient@example.com'],
51+
'subject' => 'Hello',
52+
]);
53+
```
54+
55+
Batch sending:
56+
57+
```php
58+
$response = $email->sendBatch([
59+
[
60+
'from' => 'sender@example.com',
61+
'to' => ['recipient@example.com'],
62+
'subject' => 'Hello',
63+
],
64+
]);
65+
```
66+
67+
### 3. Update response handling
68+
69+
Sending responses are now typed resource objects with IDE autocomplete.
70+
71+
Before:
72+
73+
```php
74+
$response = $lettermint->email->send();
75+
76+
$messageId = $response['message_id'];
77+
$status = $response['status'];
78+
```
79+
80+
After:
81+
82+
```php
83+
$response = $email->send();
84+
85+
$messageId = $response->message_id;
86+
$status = $response->status;
87+
```
88+
89+
Array access is still available:
90+
91+
```php
92+
$messageId = $response['message_id'];
93+
```
94+
95+
Use `toArray()` when passing responses to existing array-based code:
96+
97+
```php
98+
$payload = $response->toArray();
99+
```
100+
101+
Batch responses are also typed:
102+
103+
```php
104+
$response = $email->sendBatch($messages);
105+
106+
$firstMessageId = $response->data[0]->message_id;
107+
```
108+
109+
To keep old array-style processing:
110+
111+
```php
112+
$response = $email->sendBatch($messages)->toArray();
113+
114+
$firstMessageId = $response['data'][0]['message_id'];
115+
```
116+
117+
### 4. Update ping checks
118+
119+
`ping()` now returns the raw API ping response as a string.
120+
121+
Before:
122+
123+
```php
124+
if ($lettermint->email->ping() === 200) {
125+
// Sending API reachable
126+
}
127+
```
128+
129+
After:
130+
131+
```php
132+
if ($email->ping() === 'pong') {
133+
// Sending API reachable
134+
}
135+
```
136+
137+
### 5. Search and replace checklist
138+
139+
Search your codebase for:
140+
141+
```text
142+
new Lettermint\Lettermint(
143+
->email
144+
['message_id']
145+
['status']
146+
sendBatch(
147+
ping() === 200
148+
```
149+
150+
Then update response handling to use typed properties or `toArray()`.
151+
152+
### Notes
153+
154+
The main migration risk is code that assumes SDK responses are arrays. Most of that code can be migrated by either using property access or appending `->toArray()` at the SDK boundary.
155+
156+
Version 2 also adds a new full API client via `Lettermint::api($apiToken)`, but this is new functionality rather than a migration requirement from v1.

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"analyse": "vendor/bin/phpstan analyse",
4747
"test": "vendor/bin/pest",
4848
"test-coverage": "vendor/bin/pest --coverage",
49-
"format": "php-cs-fixer fix"
49+
"format": "pint"
5050
},
5151
"config": {
5252
"sort-packages": true,

src/Client/ApiClient.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
namespace Lettermint\Client;
4+
5+
use Lettermint\Client\Auth\TeamBearerTokenAuth;
6+
use Lettermint\Endpoints\DomainsEndpoint;
7+
use Lettermint\Endpoints\MessagesEndpoint;
8+
use Lettermint\Endpoints\ProjectsEndpoint;
9+
use Lettermint\Endpoints\RoutesEndpoint;
10+
use Lettermint\Endpoints\StatsEndpoint;
11+
use Lettermint\Endpoints\SuppressionsEndpoint;
12+
use Lettermint\Endpoints\TeamEndpoint;
13+
use Lettermint\Endpoints\WebhooksEndpoint;
14+
15+
/**
16+
* @property-read DomainsEndpoint $domains Access domain operations.
17+
* @property-read MessagesEndpoint $messages Access message operations.
18+
* @property-read ProjectsEndpoint $projects Access project operations.
19+
* @property-read RoutesEndpoint $routes Access route operations.
20+
* @property-read StatsEndpoint $stats Access statistics operations.
21+
* @property-read SuppressionsEndpoint $suppressions Access suppression operations.
22+
* @property-read TeamEndpoint $team Access team operations.
23+
* @property-read WebhooksEndpoint $webhooks Access webhook operations.
24+
*/
25+
class ApiClient
26+
{
27+
private HttpClient $httpClient;
28+
29+
private array $endpoints = [];
30+
31+
protected array $endpointRegistry = [
32+
'domains' => DomainsEndpoint::class,
33+
'messages' => MessagesEndpoint::class,
34+
'projects' => ProjectsEndpoint::class,
35+
'routes' => RoutesEndpoint::class,
36+
'stats' => StatsEndpoint::class,
37+
'suppressions' => SuppressionsEndpoint::class,
38+
'team' => TeamEndpoint::class,
39+
'webhooks' => WebhooksEndpoint::class,
40+
];
41+
42+
public function __construct(string $apiToken, ?string $baseUrl = null)
43+
{
44+
$this->httpClient = new HttpClient(
45+
new TeamBearerTokenAuth($apiToken),
46+
$baseUrl ?? 'https://api.lettermint.co/v1'
47+
);
48+
}
49+
50+
public function __get($name)
51+
{
52+
if (isset($this->endpoints[$name])) {
53+
return $this->endpoints[$name];
54+
}
55+
56+
if (array_key_exists($name, $this->endpointRegistry)) {
57+
$class = $this->endpointRegistry[$name];
58+
$this->endpoints[$name] = new $class($this->httpClient);
59+
60+
return $this->endpoints[$name];
61+
}
62+
63+
throw new \InvalidArgumentException("Unknown endpoint: $name");
64+
}
65+
66+
public function ping(): string
67+
{
68+
return trim($this->httpClient->getRaw('/v1/ping'));
69+
}
70+
}

src/Client/Auth/AuthStrategy.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Lettermint\Client\Auth;
4+
5+
interface AuthStrategy
6+
{
7+
/**
8+
* @return array<string, string>
9+
*/
10+
public function headers(): array;
11+
12+
public function token(): string;
13+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Lettermint\Client\Auth;
4+
5+
class SendingApiTokenAuth implements AuthStrategy
6+
{
7+
public function __construct(private readonly string $apiToken) {}
8+
9+
public function headers(): array
10+
{
11+
return ['x-lettermint-token' => $this->apiToken];
12+
}
13+
14+
public function token(): string
15+
{
16+
return $this->apiToken;
17+
}
18+
}

0 commit comments

Comments
 (0)