@eusend_dev/sdk 0.1.0
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 +536 -0
- package/dist/index.cjs +385 -0
- package/dist/index.d.cts +418 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +418 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +386 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
# eusend
|
|
2
|
+
|
|
3
|
+
Official Node.js SDK for the [Eusend](https://eusend.dev) API — the EU-native transactional email platform.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @eusend_dev/sdk
|
|
7
|
+
# or
|
|
8
|
+
bun add @eusend_dev/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Getting started
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { Eusend } from '@eusend_dev/sdk'
|
|
17
|
+
|
|
18
|
+
const client = new Eusend('eu_live_...')
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Your API key can also be set via the `EUSEND_API_KEY` environment variable, in which case the constructor argument can be omitted:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
const client = new Eusend()
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Emails
|
|
30
|
+
|
|
31
|
+
### Send an email
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
const { data, error } = await client.emails.send({
|
|
35
|
+
from: 'you@yourdomain.com',
|
|
36
|
+
to: 'user@example.com',
|
|
37
|
+
subject: 'Hello',
|
|
38
|
+
html: '<p>Hello world</p>',
|
|
39
|
+
text: 'Hello world',
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
console.log(data?.id) // em_...
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
#### Options
|
|
46
|
+
|
|
47
|
+
| Field | Type | Description |
|
|
48
|
+
|-------|------|-------------|
|
|
49
|
+
| `from` | `string` | Sender email address |
|
|
50
|
+
| `to` | `string \| string[]` | Recipient(s) |
|
|
51
|
+
| `cc` | `string \| string[]` | CC recipient(s) |
|
|
52
|
+
| `bcc` | `string \| string[]` | BCC recipient(s) |
|
|
53
|
+
| `replyTo` | `string \| string[]` | Reply-to address(es) |
|
|
54
|
+
| `subject` | `string` | Email subject |
|
|
55
|
+
| `html` | `string` | HTML body |
|
|
56
|
+
| `text` | `string` | Plain text body |
|
|
57
|
+
| `templateId` | `string` | ID of a saved template |
|
|
58
|
+
| `variables` | `Record<string, unknown>` | Template variable substitutions |
|
|
59
|
+
| `headers` | `Record<string, string>` | Custom email headers |
|
|
60
|
+
| `trackOpens` | `boolean` | Track open events (default: `true`) |
|
|
61
|
+
| `trackClicks` | `boolean` | Track click events (default: `true`) |
|
|
62
|
+
|
|
63
|
+
At least one of `html`, `text`, or `templateId` is required.
|
|
64
|
+
|
|
65
|
+
### Idempotent sends
|
|
66
|
+
|
|
67
|
+
Pass an `idempotencyKey` to safely retry without sending duplicates. If a request with the same key was already accepted, the original email ID is returned.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
const { data } = await client.emails.send(
|
|
71
|
+
{
|
|
72
|
+
from: 'you@yourdomain.com',
|
|
73
|
+
to: 'user@example.com',
|
|
74
|
+
subject: 'Your receipt',
|
|
75
|
+
html: '<p>Thanks for your order.</p>',
|
|
76
|
+
},
|
|
77
|
+
{ idempotencyKey: `receipt-${orderId}` },
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Send a batch
|
|
82
|
+
|
|
83
|
+
Up to 100 emails in a single request.
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
const { data } = await client.emails.batch([
|
|
87
|
+
{
|
|
88
|
+
from: 'you@yourdomain.com',
|
|
89
|
+
to: 'alice@example.com',
|
|
90
|
+
subject: 'Hello Alice',
|
|
91
|
+
html: '<p>Hi Alice</p>',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
from: 'you@yourdomain.com',
|
|
95
|
+
to: 'bob@example.com',
|
|
96
|
+
subject: 'Hello Bob',
|
|
97
|
+
html: '<p>Hi Bob</p>',
|
|
98
|
+
},
|
|
99
|
+
])
|
|
100
|
+
|
|
101
|
+
console.log(data?.data) // [{ id: '...' }, { id: '...' }]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Retrieve an email
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const { data } = await client.emails.get('em_...')
|
|
108
|
+
|
|
109
|
+
console.log(data?.status) // 'delivered'
|
|
110
|
+
console.log(data?.events) // [{ type: 'sent', ... }, { type: 'delivered', ... }]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### List emails
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
const { data } = await client.emails.list({ limit: 20 })
|
|
117
|
+
|
|
118
|
+
console.log(data?.data) // array of emails
|
|
119
|
+
console.log(data?.nextCursor) // pass as cursor to fetch the next page
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### Filtering
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
// By status
|
|
126
|
+
await client.emails.list({ status: 'delivered' })
|
|
127
|
+
|
|
128
|
+
// By sender
|
|
129
|
+
await client.emails.list({ from: 'you@yourdomain.com' })
|
|
130
|
+
|
|
131
|
+
// By recipient
|
|
132
|
+
await client.emails.list({ to: 'user@example.com' })
|
|
133
|
+
|
|
134
|
+
// Pagination
|
|
135
|
+
await client.emails.list({ limit: 50, cursor: data.nextCursor })
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Available statuses: `queued` `sending` `sent` `delivered` `bounced` `complained` `suppressed` `failed`
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Domains
|
|
143
|
+
|
|
144
|
+
### Add a domain
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
const { data } = await client.domains.create('yourdomain.com')
|
|
148
|
+
|
|
149
|
+
// DNS records to add to your domain
|
|
150
|
+
console.log(data?.dkim) // { type: 'TXT', name: 'eusend._domainkey.yourdomain.com', value: '...' }
|
|
151
|
+
console.log(data?.spf) // { type: 'TXT', name: 'yourdomain.com', value: '...' }
|
|
152
|
+
console.log(data?.dmarc) // { type: 'TXT', name: '_dmarc.yourdomain.com', value: '...' }
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Verify a domain
|
|
156
|
+
|
|
157
|
+
After adding the DNS records, trigger verification:
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
await client.domains.verify(domainId)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### List domains
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
const { data } = await client.domains.list()
|
|
167
|
+
// [{ id, name, status: 'verified', createdAt }]
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Get a domain
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
const { data } = await client.domains.get(domainId)
|
|
174
|
+
// { id, name, dkimPublicKey, dkimSelector, status, createdAt, verifiedAt }
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Delete a domain
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
await client.domains.delete(domainId)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## API Keys
|
|
186
|
+
|
|
187
|
+
### Create an API key
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
const { data } = await client.apiKeys.create({ name: 'Production' })
|
|
191
|
+
|
|
192
|
+
console.log(data?.key) // eu_live_... — only returned once, store it securely
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Pass `testMode: true` to create a sandbox key. Emails sent with a test key are accepted and tracked but never delivered.
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
const { data } = await client.apiKeys.create({ name: 'Sandbox', testMode: true })
|
|
199
|
+
// data.key → 'eu_test_...'
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### List API keys
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
const { data } = await client.apiKeys.list()
|
|
206
|
+
// [{ id, name, prefix, testMode, createdAt, lastUsedAt }]
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The full key is never returned after creation — only the prefix (e.g. `eu_live_Lx_e`).
|
|
210
|
+
|
|
211
|
+
### Delete an API key
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
await client.apiKeys.delete(keyId)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Audiences & Contacts
|
|
220
|
+
|
|
221
|
+
### Create an audience
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
const { data } = await client.audiences.create('Newsletter')
|
|
225
|
+
const audienceId = data!.id
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### List audiences
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
const { data } = await client.audiences.list()
|
|
232
|
+
// [{ id, name, createdAt, contactCount }]
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Delete an audience
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
await client.audiences.delete(audienceId)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Add a contact
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
const { data } = await client.audiences.createContact(audienceId, {
|
|
245
|
+
email: 'user@example.com',
|
|
246
|
+
firstName: 'Jane',
|
|
247
|
+
lastName: 'Smith',
|
|
248
|
+
})
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
If a contact with that email already exists in the audience, it will be updated instead.
|
|
252
|
+
|
|
253
|
+
### Bulk import contacts
|
|
254
|
+
|
|
255
|
+
Up to 1,000 contacts per call. Existing contacts are upserted.
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
const { data } = await client.audiences.batchCreateContacts(audienceId, {
|
|
259
|
+
contacts: [
|
|
260
|
+
{ email: 'alice@example.com', firstName: 'Alice' },
|
|
261
|
+
{ email: 'bob@example.com', firstName: 'Bob' },
|
|
262
|
+
],
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
console.log(data?.count) // 2
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### List contacts
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
const { data } = await client.audiences.listContacts(audienceId, { limit: 100 })
|
|
272
|
+
|
|
273
|
+
// Filter by subscription status
|
|
274
|
+
await client.audiences.listContacts(audienceId, { subscribed: true })
|
|
275
|
+
await client.audiences.listContacts(audienceId, { subscribed: false })
|
|
276
|
+
|
|
277
|
+
// Search by email
|
|
278
|
+
await client.audiences.listContacts(audienceId, { search: 'gmail.com' })
|
|
279
|
+
|
|
280
|
+
// Pagination
|
|
281
|
+
await client.audiences.listContacts(audienceId, { cursor: data.nextCursor })
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Get a contact
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
const { data } = await client.audiences.getContact(audienceId, contactId)
|
|
288
|
+
// { id, audienceId, email, firstName, lastName, status, unsubscribedAt, createdAt, updatedAt }
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Update a contact
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
// Update name
|
|
295
|
+
await client.audiences.updateContact(audienceId, contactId, {
|
|
296
|
+
firstName: 'Janet',
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
// Unsubscribe
|
|
300
|
+
await client.audiences.updateContact(audienceId, contactId, {
|
|
301
|
+
unsubscribed: true,
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
// Re-subscribe
|
|
305
|
+
await client.audiences.updateContact(audienceId, contactId, {
|
|
306
|
+
unsubscribed: false,
|
|
307
|
+
})
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Delete a contact
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
await client.audiences.deleteContact(audienceId, contactId)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Templates
|
|
319
|
+
|
|
320
|
+
Templates let you define reusable email layouts with `{{variable}}` placeholders that are substituted at send time.
|
|
321
|
+
|
|
322
|
+
### Create a template (HTML)
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
const { data } = await client.templates.create({
|
|
326
|
+
name: 'Welcome email',
|
|
327
|
+
subject: 'Welcome, {{name}}!',
|
|
328
|
+
html: '<h1>Hi {{name}}</h1><p>Welcome to {{product}}.</p>',
|
|
329
|
+
})
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Create a template (React)
|
|
333
|
+
|
|
334
|
+
Pass JSX using `@react-email/components`. The server renders it to email-safe HTML at save time, with `{{variable}}` placeholders preserved for send-time substitution.
|
|
335
|
+
|
|
336
|
+
```ts
|
|
337
|
+
const { data } = await client.templates.create({
|
|
338
|
+
name: 'Order confirmation',
|
|
339
|
+
subject: 'Your order {{order_id}} is confirmed',
|
|
340
|
+
reactSource: `
|
|
341
|
+
<Html>
|
|
342
|
+
<Head />
|
|
343
|
+
<Preview>Order {{order_id}} confirmed</Preview>
|
|
344
|
+
<Body style={{ backgroundColor: '#f4f4f5', fontFamily: 'system-ui, sans-serif' }}>
|
|
345
|
+
<Container style={{ maxWidth: '520px', margin: '40px auto', backgroundColor: '#fff', borderRadius: '8px' }}>
|
|
346
|
+
<Section style={{ padding: '32px' }}>
|
|
347
|
+
<Heading>Order confirmed ✓</Heading>
|
|
348
|
+
<Text>Hi {{first_name}}, your order <strong>{{order_id}}</strong> is on its way.</Text>
|
|
349
|
+
<Button href="https://yourapp.com/orders/{{order_id}}">View order</Button>
|
|
350
|
+
</Section>
|
|
351
|
+
</Container>
|
|
352
|
+
</Body>
|
|
353
|
+
</Html>`,
|
|
354
|
+
})
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Send using a template
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
await client.emails.send({
|
|
361
|
+
from: 'you@yourdomain.com',
|
|
362
|
+
to: 'user@example.com',
|
|
363
|
+
templateId: data!.id,
|
|
364
|
+
variables: {
|
|
365
|
+
first_name: 'Jane',
|
|
366
|
+
order_id: 'ORD-1234',
|
|
367
|
+
},
|
|
368
|
+
})
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### List, get, update, delete
|
|
372
|
+
|
|
373
|
+
```ts
|
|
374
|
+
await client.templates.list()
|
|
375
|
+
await client.templates.get(templateId)
|
|
376
|
+
await client.templates.update(templateId, { name: 'New name', subject: 'New subject' })
|
|
377
|
+
await client.templates.delete(templateId)
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## Webhooks
|
|
383
|
+
|
|
384
|
+
Receive real-time events when email statuses change.
|
|
385
|
+
|
|
386
|
+
### Create a webhook
|
|
387
|
+
|
|
388
|
+
```ts
|
|
389
|
+
const { data } = await client.webhooks.create({
|
|
390
|
+
url: 'https://yourapp.com/webhooks/eusend',
|
|
391
|
+
events: ['email.sent', 'email.delivered', 'email.bounced', 'email.complained'],
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
console.log(data?.secret) // signing secret — only returned once, store it securely
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Pass `'*'` in the events array to subscribe to all events.
|
|
398
|
+
|
|
399
|
+
Available events: `email.sent` `email.delivered` `email.bounced` `email.complained` `email.opened` `email.clicked`
|
|
400
|
+
|
|
401
|
+
### Verifying webhook signatures
|
|
402
|
+
|
|
403
|
+
Every delivery is signed with HMAC-SHA256. Verify the signature before processing:
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
import { createHmac, timingSafeEqual } from 'crypto'
|
|
407
|
+
|
|
408
|
+
function verifyWebhook(req: Request, secret: string): boolean {
|
|
409
|
+
const webhookId = req.headers.get('x-webhook-id') ?? ''
|
|
410
|
+
const timestamp = req.headers.get('x-webhook-timestamp') ?? ''
|
|
411
|
+
const signature = req.headers.get('x-webhook-signature') ?? ''
|
|
412
|
+
|
|
413
|
+
const body = await req.text()
|
|
414
|
+
const expected = 'v1,' + createHmac('sha256', secret)
|
|
415
|
+
.update(`${webhookId}.${timestamp}.${body}`)
|
|
416
|
+
.digest('base64')
|
|
417
|
+
|
|
418
|
+
return timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### List, get, update, delete
|
|
423
|
+
|
|
424
|
+
```ts
|
|
425
|
+
await client.webhooks.list()
|
|
426
|
+
await client.webhooks.get(webhookId) // includes recent deliveries
|
|
427
|
+
await client.webhooks.update(webhookId, { events: ['email.bounced'] })
|
|
428
|
+
await client.webhooks.delete(webhookId)
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## Broadcasts
|
|
434
|
+
|
|
435
|
+
Send a single email to every contact in an audience.
|
|
436
|
+
|
|
437
|
+
### Create a broadcast
|
|
438
|
+
|
|
439
|
+
```ts
|
|
440
|
+
const { data } = await client.broadcasts.create({
|
|
441
|
+
name: 'May newsletter',
|
|
442
|
+
audienceId: 'aud_...',
|
|
443
|
+
from: 'Sivert <hello@yourdomain.com>',
|
|
444
|
+
subject: 'May update',
|
|
445
|
+
html: '<p>Hi {{first_name}}, here is this month's update...</p>',
|
|
446
|
+
})
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
`{{first_name}}`, `{{last_name}}`, `{{full_name}}`, and `{{email}}` are automatically available per recipient. Custom variables can be defined on the broadcast and are merged with per-recipient data.
|
|
450
|
+
|
|
451
|
+
### Send a broadcast
|
|
452
|
+
|
|
453
|
+
```ts
|
|
454
|
+
await client.broadcasts.send(broadcastId)
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### Schedule a broadcast
|
|
458
|
+
|
|
459
|
+
```ts
|
|
460
|
+
await client.broadcasts.send(broadcastId, {
|
|
461
|
+
scheduledAt: '2026-06-01T09:00:00.000Z',
|
|
462
|
+
})
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### Cancel a broadcast
|
|
466
|
+
|
|
467
|
+
```ts
|
|
468
|
+
await client.broadcasts.cancel(broadcastId)
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### List, get, update, delete
|
|
472
|
+
|
|
473
|
+
```ts
|
|
474
|
+
await client.broadcasts.list()
|
|
475
|
+
await client.broadcasts.get(broadcastId) // includes delivery stats
|
|
476
|
+
await client.broadcasts.update(broadcastId, { subject: 'Updated subject' })
|
|
477
|
+
await client.broadcasts.delete(broadcastId)
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## Error handling
|
|
483
|
+
|
|
484
|
+
Every method returns `{ data, error, headers }`. On success `error` is `null`; on failure `data` is `null`.
|
|
485
|
+
|
|
486
|
+
```ts
|
|
487
|
+
const { data, error } = await client.emails.send({ ... })
|
|
488
|
+
|
|
489
|
+
if (error) {
|
|
490
|
+
console.error(error.name) // 'MONTHLY_LIMIT_EXCEEDED'
|
|
491
|
+
console.error(error.message) // 'Monthly send limit exceeded'
|
|
492
|
+
console.error(error.statusCode) // 429
|
|
493
|
+
} else {
|
|
494
|
+
console.log(data.id)
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
#### Error codes
|
|
499
|
+
|
|
500
|
+
| Code | Status | Description |
|
|
501
|
+
|------|--------|-------------|
|
|
502
|
+
| `UNAUTHORIZED` | 401 | Invalid or missing API key |
|
|
503
|
+
| `FORBIDDEN` | 403 | Action not allowed on your plan |
|
|
504
|
+
| `NOT_FOUND` | 404 | Resource not found |
|
|
505
|
+
| `VALIDATION_ERROR` | 400 | Invalid request body |
|
|
506
|
+
| `CONFLICT` | 409 | Resource already exists |
|
|
507
|
+
| `RATE_LIMITED` | 429 | Too many requests |
|
|
508
|
+
| `MONTHLY_LIMIT_EXCEEDED` | 429 | Monthly send quota reached |
|
|
509
|
+
| `DAILY_LIMIT_EXCEEDED` | 429 | Daily send quota reached (free plan) |
|
|
510
|
+
| `PLAN_LIMIT_EXCEEDED` | 403 | Feature not available on your plan |
|
|
511
|
+
| `ALL_SUPPRESSED` | 422 | All recipients are on the suppression list |
|
|
512
|
+
| `INTERNAL_ERROR` | 500 | Server error |
|
|
513
|
+
| `application_error` | `null` | Network failure — request never reached the server |
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## TypeScript
|
|
518
|
+
|
|
519
|
+
The SDK is written in TypeScript and ships with full type definitions. All request options, response shapes, and error codes are typed.
|
|
520
|
+
|
|
521
|
+
```ts
|
|
522
|
+
import type {
|
|
523
|
+
SendEmailOptions,
|
|
524
|
+
Email,
|
|
525
|
+
EmailStatus,
|
|
526
|
+
EusendError,
|
|
527
|
+
EusendResponse,
|
|
528
|
+
} from '@eusend_dev/sdk'
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
## Requirements
|
|
534
|
+
|
|
535
|
+
- Node.js 18 or later (uses the native `fetch` API)
|
|
536
|
+
- An Eusend account and API key — [eusend.dev](https://eusend.dev)
|