@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 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
+ ```