@develemail/sdk 0.1.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +211 -0
- package/dist/index.cjs +593 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +462 -14
- package/dist/index.d.ts +462 -14
- package/dist/index.js +589 -25
- package/dist/index.js.map +1 -1
- package/package.json +19 -3
package/README.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# @develemail/sdk
|
|
2
|
+
|
|
3
|
+
Node.js/TypeScript client SDK for the DevelEmail transactional email API. Zero runtime dependencies — uses the global `fetch` API (Node >= 18).
|
|
4
|
+
|
|
5
|
+
## Quickstart
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { DevelEmail } from '@develemail/sdk';
|
|
9
|
+
|
|
10
|
+
const client = new DevelEmail({
|
|
11
|
+
apiKey: 'your-api-key',
|
|
12
|
+
baseUrl: 'https://your-develemail-instance.com',
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Send an email
|
|
16
|
+
const { id, status } = await client.emails.send({
|
|
17
|
+
from: 'hello@yourdomain.com',
|
|
18
|
+
to: 'user@example.com',
|
|
19
|
+
subject: 'Welcome!',
|
|
20
|
+
html: '<h1>Hello!</h1>',
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Check delivery status
|
|
24
|
+
const email = await client.emails.get(id);
|
|
25
|
+
console.log(email.status); // 'queued' | 'delivered' | ...
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Batch send
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
const result = await client.emails.batch([
|
|
32
|
+
{
|
|
33
|
+
from: 'hello@yourdomain.com',
|
|
34
|
+
to: 'a@example.com',
|
|
35
|
+
subject: 'Hi',
|
|
36
|
+
text: 'Hello A',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
from: 'hello@yourdomain.com',
|
|
40
|
+
to: 'b@example.com',
|
|
41
|
+
subject: 'Hi',
|
|
42
|
+
text: 'Hello B',
|
|
43
|
+
},
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
console.log(result.created); // 2
|
|
47
|
+
console.log(result.errors); // 0
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
A failed item carries a stable, machine-readable `code` next to its `error`
|
|
51
|
+
message, so you can retry selectively without matching English text:
|
|
52
|
+
`domain_not_verified`, `reserved_recipient_domain`, `mixed_sandbox_recipients`,
|
|
53
|
+
`recipient_cooldown`, `daily_limit_reached`, `attachment_too_large`,
|
|
54
|
+
`attachment_type_blocked`, `invalid_attachment`, `invalid_sender_domain`,
|
|
55
|
+
`template_not_found`, `missing_subject`, and `send_failed` for anything else.
|
|
56
|
+
|
|
57
|
+
## Attachments
|
|
58
|
+
|
|
59
|
+
Pass a `Buffer`/`Uint8Array` for `content` and the SDK base64-encodes it for
|
|
60
|
+
you — or pass an already-base64 `string` directly. Server caps: max 10
|
|
61
|
+
attachments per email, 5 MiB per file, 7 MiB combined decoded size.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { readFile } from 'node:fs/promises';
|
|
65
|
+
|
|
66
|
+
const csv = await readFile('export.csv');
|
|
67
|
+
|
|
68
|
+
await client.emails.send({
|
|
69
|
+
from: 'hello@yourdomain.com',
|
|
70
|
+
to: 'user@example.com',
|
|
71
|
+
subject: 'Your export is ready',
|
|
72
|
+
text: 'Attached: export.csv',
|
|
73
|
+
attachments: [
|
|
74
|
+
{ filename: 'export.csv', content: csv, contentType: 'text/csv' },
|
|
75
|
+
],
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Automatic retry + idempotency
|
|
80
|
+
|
|
81
|
+
Sends automatically retry on network errors, 5xx, and 429 responses with exponential backoff. Each send generates a unique `Idempotency-Key` header so retries never produce duplicate emails.
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// Custom retry count (default: 3)
|
|
85
|
+
const client = new DevelEmail({
|
|
86
|
+
apiKey: 'your-api-key',
|
|
87
|
+
baseUrl: 'https://your-instance.com',
|
|
88
|
+
maxRetries: 5,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Supply your own idempotency key
|
|
92
|
+
await client.emails.send(
|
|
93
|
+
{
|
|
94
|
+
from: 'hello@yourdomain.com',
|
|
95
|
+
to: 'user@example.com',
|
|
96
|
+
subject: 'Hi',
|
|
97
|
+
text: 'Hello',
|
|
98
|
+
},
|
|
99
|
+
{ idempotencyKey: 'order-confirmation-12345' },
|
|
100
|
+
);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Error handling
|
|
104
|
+
|
|
105
|
+
Error codes are listed in the [API overview](https://docs.develemail.com/api-overview#errors).
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { DevelEmail, DevelEmailError } from '@develemail/sdk';
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
await client.emails.send({ ... });
|
|
112
|
+
} catch (err) {
|
|
113
|
+
if (err instanceof DevelEmailError) {
|
|
114
|
+
console.log(err.status); // HTTP status code
|
|
115
|
+
console.log(err.code); // 'quota_exceeded', 'domain_not_verified', etc.
|
|
116
|
+
console.log(err.message); // Human-readable message
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`err.code` is the body's machine-readable `code` field when present, otherwise
|
|
122
|
+
its `error` field. `{ "error": "Forbidden", "code": "EMAIL_NOT_VERIFIED" }`
|
|
123
|
+
gives `err.code === 'EMAIL_NOT_VERIFIED'` (earlier versions returned
|
|
124
|
+
`'Forbidden'`). `err.details.code` still carries the same value.
|
|
125
|
+
|
|
126
|
+
A `quota_exceeded` (429) response body also carries `upgrade_url` when billing
|
|
127
|
+
is enabled on the instance — link your user there to raise their plan.
|
|
128
|
+
|
|
129
|
+
## Documentation
|
|
130
|
+
|
|
131
|
+
Full method reference: [https://docs.develemail.com/sdk-reference](https://docs.develemail.com/sdk-reference).
|
|
132
|
+
|
|
133
|
+
## Testing without delivering
|
|
134
|
+
|
|
135
|
+
Send to any address at `sandbox.develemail.com` (e.g.
|
|
136
|
+
`e2e-42@sandbox.develemail.com`) from automated tests. The API accepts the
|
|
137
|
+
send, returns a normal success response and logs it with status `sandboxed`,
|
|
138
|
+
but nothing is handed to Postfix. Sandbox sends skip duplicate detection and
|
|
139
|
+
are excluded from bounce rates, digests and warmup signals.
|
|
140
|
+
|
|
141
|
+
A send must be all-sandbox or all-real: mixing them is rejected with a
|
|
142
|
+
`400` and `error: "mixed_sandbox_recipients"`.
|
|
143
|
+
|
|
144
|
+
## Domains & Templates
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
const domains = await client.domains.list();
|
|
148
|
+
const domain = await client.domains.get('domain-id');
|
|
149
|
+
|
|
150
|
+
const templates = await client.templates.list();
|
|
151
|
+
const template = await client.templates.get('template-id');
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## SMS
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
const { id, status } = await client.sms.send({
|
|
158
|
+
to: '+15551234567',
|
|
159
|
+
from: '+15557654321',
|
|
160
|
+
body: 'Your order has shipped!',
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Or render an SMS template (created via the API) server-side
|
|
164
|
+
await client.sms.send({
|
|
165
|
+
to: '+15551234567',
|
|
166
|
+
templateName: 'otp-code',
|
|
167
|
+
variables: { code: '482913' },
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Up to 100 messages per request; each succeeds or fails on its own
|
|
171
|
+
const { results, created, errors } = await client.sms.sendBatch([
|
|
172
|
+
{ to: '+15551234567', body: 'Reminder: appointment at 3pm' },
|
|
173
|
+
{ to: '+15557654321', body: 'Reminder: appointment at 4pm' },
|
|
174
|
+
]);
|
|
175
|
+
// results[i] is { index, id, status } or { index, error, code }
|
|
176
|
+
|
|
177
|
+
const message = await client.sms.get(id);
|
|
178
|
+
console.log(message.status); // 'queued' | 'delivered' | ...
|
|
179
|
+
|
|
180
|
+
const { data, nextCursor } = await client.sms.list({
|
|
181
|
+
status: 'delivered',
|
|
182
|
+
limit: 25,
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Numbers (`to` and `from`) must be in E.164 format (e.g. `+15551234567`), and a
|
|
187
|
+
message body (or rendered template) can be at most 1600 characters.
|
|
188
|
+
`send` and `sendBatch` set an `Idempotency-Key` for you (override it with
|
|
189
|
+
`{ idempotencyKey }`); `POST /v1/sms/batch` returns `207` when any item fails
|
|
190
|
+
and `201` otherwise. Suppressed recipients return `status: 'suppressed'` for
|
|
191
|
+
that item and are not sent.
|
|
192
|
+
|
|
193
|
+
`POST /v1/sms` honours the `Idempotency-Key` header (1–255 characters): retrying
|
|
194
|
+
with the same key and body sends exactly one SMS and replays the original
|
|
195
|
+
response with `Idempotency-Replayed: true`. Reusing a key with a different body
|
|
196
|
+
returns `422 idempotency_key_reuse`, and a retry while the first request is
|
|
197
|
+
still in flight returns `409 idempotency_conflict`. SMS keys are scoped
|
|
198
|
+
separately from email keys, so the same key on `/v1/emails` never collides. The
|
|
199
|
+
SDK's `sms.send` sets this header for you, reusing the same key across retries.
|
|
200
|
+
|
|
201
|
+
## Webhook verification
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { verifyWebhookSignature } from '@develemail/sdk';
|
|
205
|
+
|
|
206
|
+
const valid = await verifyWebhookSignature(
|
|
207
|
+
rawBody,
|
|
208
|
+
signatureHeader,
|
|
209
|
+
webhookSecret,
|
|
210
|
+
);
|
|
211
|
+
```
|