@vaultsandbox/client 0.5.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/LICENSE +21 -0
- package/README.md +612 -0
- package/dist/client.d.ts +231 -0
- package/dist/client.js +432 -0
- package/dist/client.js.map +1 -0
- package/dist/crypto/constants.d.ts +13 -0
- package/dist/crypto/constants.js +14 -0
- package/dist/crypto/constants.js.map +1 -0
- package/dist/crypto/decrypt.d.ts +41 -0
- package/dist/crypto/decrypt.js +112 -0
- package/dist/crypto/decrypt.js.map +1 -0
- package/dist/crypto/keypair.d.ts +39 -0
- package/dist/crypto/keypair.js +109 -0
- package/dist/crypto/keypair.js.map +1 -0
- package/dist/crypto/signature.d.ts +28 -0
- package/dist/crypto/signature.js +89 -0
- package/dist/crypto/signature.js.map +1 -0
- package/dist/crypto/utils.d.ts +28 -0
- package/dist/crypto/utils.js +60 -0
- package/dist/crypto/utils.js.map +1 -0
- package/dist/email.d.ts +63 -0
- package/dist/email.js +186 -0
- package/dist/email.js.map +1 -0
- package/dist/http/api-client.d.ts +145 -0
- package/dist/http/api-client.js +242 -0
- package/dist/http/api-client.js.map +1 -0
- package/dist/inbox.d.ts +120 -0
- package/dist/inbox.js +243 -0
- package/dist/inbox.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/strategies/delivery-strategy.d.ts +29 -0
- package/dist/strategies/delivery-strategy.js +2 -0
- package/dist/strategies/delivery-strategy.js.map +1 -0
- package/dist/strategies/polling-strategy.d.ts +36 -0
- package/dist/strategies/polling-strategy.js +146 -0
- package/dist/strategies/polling-strategy.js.map +1 -0
- package/dist/strategies/sse-strategy.d.ts +49 -0
- package/dist/strategies/sse-strategy.js +266 -0
- package/dist/strategies/sse-strategy.js.map +1 -0
- package/dist/types/index.d.ts +434 -0
- package/dist/types/index.js +127 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/email-utils.d.ts +19 -0
- package/dist/utils/email-utils.js +92 -0
- package/dist/utils/email-utils.js.map +1 -0
- package/dist/utils/sleep.d.ts +6 -0
- package/dist/utils/sleep.js +9 -0
- package/dist/utils/sleep.js.map +1 -0
- package/package.json +85 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Antero
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
<picture>
|
|
2
|
+
<source media="(prefers-color-scheme: dark)" srcset="./assets/logo-dark.svg">
|
|
3
|
+
<source media="(prefers-color-scheme: light)" srcset="./assets/logo-light.svg">
|
|
4
|
+
<img alt="VaultSandbox" src="./assets/logo-dark.svg">
|
|
5
|
+
</picture>
|
|
6
|
+
|
|
7
|
+
# @vaultsandbox/client
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@vaultsandbox/client)
|
|
10
|
+
[](https://github.com/vaultsandbox/client-node/actions/workflows/ci.yml)
|
|
11
|
+
[](https://opensource.org/licenses/MIT)
|
|
12
|
+
[](https://nodejs.org/)
|
|
13
|
+
|
|
14
|
+
**Production-like email testing. Self-hosted & secure.**
|
|
15
|
+
|
|
16
|
+
The official Node.js SDK for [VaultSandbox Gateway](https://github.com/vaultsandbox/gateway) — a secure, receive-only SMTP server for QA/testing environments. This SDK abstracts quantum-safe encryption complexity, making email testing workflows transparent and effortless.
|
|
17
|
+
|
|
18
|
+
Stop mocking your email stack. If your app sends real emails in production, it must send real emails in testing. VaultSandbox provides isolated inboxes that behave exactly like production without exposing a single byte of customer data.
|
|
19
|
+
|
|
20
|
+
> **Node.js 20+** required. Not intended for browsers or edge runtimes.
|
|
21
|
+
|
|
22
|
+
## Why VaultSandbox?
|
|
23
|
+
|
|
24
|
+
| Feature | Simple Mocks | Public SaaS | **VaultSandbox** |
|
|
25
|
+
| :------------------ | :--------------- | :----------- | :------------------ |
|
|
26
|
+
| **TLS/SSL** | Ignored/Disabled | Partial | **Real ACME certs** |
|
|
27
|
+
| **Data Privacy** | Local only | Shared cloud | **Private VPC** |
|
|
28
|
+
| **Inbound Mail** | Outbound only | Yes | **Real MX** |
|
|
29
|
+
| **Auth (SPF/DKIM)** | None | Limited | **Full Validation** |
|
|
30
|
+
| **Crypto** | Plaintext | Varies | **Zero-Knowledge** |
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- **Quantum-Safe Encryption** — Automatic ML-KEM-768 (Kyber768) key encapsulation + AES-256-GCM encryption
|
|
35
|
+
- **Zero Crypto Knowledge Required** — All cryptographic operations are invisible to the user
|
|
36
|
+
- **Real-Time Email Delivery** — SSE-based delivery with smart polling fallback
|
|
37
|
+
- **Built for CI/CD** — Deterministic tests without sleeps, polling, or flakiness
|
|
38
|
+
- **Full Email Access** — Decrypt and access email content, headers, links, and attachments
|
|
39
|
+
- **Email Authentication** — Built-in SPF/DKIM/DMARC validation helpers
|
|
40
|
+
- **Type-Safe** — Full TypeScript support with comprehensive type definitions
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install @vaultsandbox/client
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
import { VaultSandboxClient } from '@vaultsandbox/client';
|
|
52
|
+
|
|
53
|
+
// Initialize client with your API key
|
|
54
|
+
const client = new VaultSandboxClient({
|
|
55
|
+
url: 'https://smtp.vaultsandbox.com',
|
|
56
|
+
apiKey: 'your-api-key',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Create inbox (keypair generated automatically)
|
|
60
|
+
const inbox = await client.createInbox();
|
|
61
|
+
console.log(`Send email to: ${inbox.emailAddress}`);
|
|
62
|
+
|
|
63
|
+
// Wait for email with timeout
|
|
64
|
+
const email = await inbox.waitForEmail({
|
|
65
|
+
timeout: 30000, // 30 seconds
|
|
66
|
+
subject: /Test/, // Optional filter
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Email is already decrypted - just use it!
|
|
70
|
+
console.log('From:', email.from);
|
|
71
|
+
console.log('Subject:', email.subject);
|
|
72
|
+
console.log('Text:', email.text);
|
|
73
|
+
console.log('HTML:', email.html);
|
|
74
|
+
|
|
75
|
+
// Cleanup
|
|
76
|
+
await inbox.delete();
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Usage Examples
|
|
80
|
+
|
|
81
|
+
### Testing Password Reset Emails
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
import { VaultSandboxClient } from '@vaultsandbox/client';
|
|
85
|
+
|
|
86
|
+
const client = new VaultSandboxClient({ url, apiKey });
|
|
87
|
+
const inbox = await client.createInbox();
|
|
88
|
+
|
|
89
|
+
// Trigger password reset in your app (replace with your own implementation)
|
|
90
|
+
await yourApp.requestPasswordReset(inbox.emailAddress);
|
|
91
|
+
|
|
92
|
+
// Wait for and validate the reset email
|
|
93
|
+
const email = await inbox.waitForEmail({
|
|
94
|
+
timeout: 10000,
|
|
95
|
+
subject: /Reset your password/,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Extract reset link
|
|
99
|
+
const resetLink = email.links.find((url) => url.includes('/reset-password'));
|
|
100
|
+
console.log('Reset link:', resetLink);
|
|
101
|
+
|
|
102
|
+
// Validate email authentication
|
|
103
|
+
const authValidation = email.authResults.validate();
|
|
104
|
+
// In a real test, this may not pass if the sender isn't fully configured.
|
|
105
|
+
// A robust check verifies the validation was performed and has the correct shape.
|
|
106
|
+
expect(typeof authValidation.passed).toBe('boolean');
|
|
107
|
+
expect(Array.isArray(authValidation.failures)).toBe(true);
|
|
108
|
+
|
|
109
|
+
await inbox.delete();
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Testing Email Authentication (SPF/DKIM/DMARC)
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
const email = await inbox.waitForEmail({ timeout: 5000 });
|
|
116
|
+
const validation = email.authResults.validate();
|
|
117
|
+
|
|
118
|
+
if (!validation.passed) {
|
|
119
|
+
console.error('Email authentication failed:');
|
|
120
|
+
validation.failures.forEach((reason) => {
|
|
121
|
+
console.error(` - ${reason}`);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Or check individual results. Statuses can vary based on the sending source.
|
|
126
|
+
if (email.authResults.spf?.status) {
|
|
127
|
+
expect(email.authResults.spf.status).toMatch(/pass|fail|softfail|neutral|temperror|permerror/);
|
|
128
|
+
}
|
|
129
|
+
if (email.authResults.dkim) {
|
|
130
|
+
expect(email.authResults.dkim.length).toBeGreaterThan(0);
|
|
131
|
+
}
|
|
132
|
+
if (email.authResults.dmarc?.status) {
|
|
133
|
+
expect(email.authResults.dmarc.status).toMatch(/pass|fail|neutral|temperror|permerror/);
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Extracting and Validating Links
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
const email = await inbox.waitForEmail({ subject: /Verify your email/ });
|
|
141
|
+
|
|
142
|
+
// All links are automatically extracted
|
|
143
|
+
const verifyLink = email.links.find((url) => url.includes('/verify'));
|
|
144
|
+
expect(verifyLink).toBeDefined();
|
|
145
|
+
expect(verifyLink).toContain('https://');
|
|
146
|
+
|
|
147
|
+
// Test the verification flow
|
|
148
|
+
const response = await fetch(verifyLink);
|
|
149
|
+
expect(response.ok).toBe(true);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Working with Email Attachments
|
|
153
|
+
|
|
154
|
+
Email attachments are automatically decrypted and available as `Uint8Array` buffers, ready to be processed or saved.
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
import { writeFileSync } from 'fs';
|
|
158
|
+
|
|
159
|
+
const email = await inbox.waitForEmail({ subject: /Documents Attached/ });
|
|
160
|
+
|
|
161
|
+
// Access attachments array
|
|
162
|
+
console.log(`Found ${email.attachments.length} attachments`);
|
|
163
|
+
|
|
164
|
+
// Iterate through attachments
|
|
165
|
+
for (const attachment of email.attachments) {
|
|
166
|
+
console.log(`Filename: ${attachment.filename}`);
|
|
167
|
+
console.log(`Content-Type: ${attachment.contentType}`);
|
|
168
|
+
console.log(`Size: ${attachment.size} bytes`);
|
|
169
|
+
|
|
170
|
+
if (!attachment.content) continue;
|
|
171
|
+
|
|
172
|
+
// Decode text-based attachments
|
|
173
|
+
if (attachment.contentType.includes('text')) {
|
|
174
|
+
const textContent = new TextDecoder().decode(attachment.content);
|
|
175
|
+
console.log('Content:', textContent);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Parse JSON attachments
|
|
179
|
+
if (attachment.contentType.includes('json')) {
|
|
180
|
+
const jsonContent = new TextDecoder().decode(attachment.content);
|
|
181
|
+
const data = JSON.parse(jsonContent);
|
|
182
|
+
console.log('Parsed data:', data);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Save binary files to disk
|
|
186
|
+
if (attachment.contentType.includes('pdf') || attachment.contentType.includes('image')) {
|
|
187
|
+
writeFileSync(`./downloads/${attachment.filename}`, attachment.content);
|
|
188
|
+
console.log(`Saved ${attachment.filename}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Find and verify specific attachment in tests
|
|
193
|
+
const pdfAttachment = email.attachments.find((att) => att.filename === 'invoice.pdf');
|
|
194
|
+
expect(pdfAttachment).toBeDefined();
|
|
195
|
+
expect(pdfAttachment!.contentType).toBe('application/pdf');
|
|
196
|
+
expect(pdfAttachment!.size).toBeGreaterThan(0);
|
|
197
|
+
|
|
198
|
+
// Verify attachment content exists and has expected size
|
|
199
|
+
if (pdfAttachment?.content) {
|
|
200
|
+
expect(pdfAttachment.content.length).toBe(pdfAttachment.size);
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Testing with Jest
|
|
205
|
+
|
|
206
|
+
```javascript
|
|
207
|
+
describe('Email Flow', () => {
|
|
208
|
+
let client, inbox;
|
|
209
|
+
|
|
210
|
+
beforeEach(async () => {
|
|
211
|
+
client = new VaultSandboxClient({ url, apiKey });
|
|
212
|
+
inbox = await client.createInbox();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
afterEach(async () => {
|
|
216
|
+
await inbox?.delete();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test('should receive welcome email', async () => {
|
|
220
|
+
await sendWelcomeEmail(inbox.emailAddress);
|
|
221
|
+
|
|
222
|
+
const email = await inbox.waitForEmail({
|
|
223
|
+
timeout: 5000,
|
|
224
|
+
subject: /Welcome/,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
expect(email.from).toBe('noreply@example.com');
|
|
228
|
+
expect(email.text).toContain('Thank you for signing up');
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Waiting for Multiple Emails
|
|
234
|
+
|
|
235
|
+
When testing scenarios that send multiple emails, use `waitForEmailCount()` instead of arbitrary timeouts for faster and more reliable tests:
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
test('should receive multiple notification emails', async () => {
|
|
239
|
+
// Send multiple emails
|
|
240
|
+
await sendNotifications(inbox.emailAddress, 3);
|
|
241
|
+
|
|
242
|
+
// Wait for all 3 emails to arrive (polls every 1s by default)
|
|
243
|
+
await inbox.waitForEmailCount(3, { timeout: 30000 });
|
|
244
|
+
|
|
245
|
+
// Now list and verify all emails
|
|
246
|
+
const emails = await inbox.listEmails();
|
|
247
|
+
expect(emails.length).toBe(3);
|
|
248
|
+
expect(emails[0].subject).toContain('Notification');
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Real-time Monitoring
|
|
253
|
+
|
|
254
|
+
For scenarios where you need to process emails as they arrive without blocking, you can use the `onNewEmail` subscription.
|
|
255
|
+
|
|
256
|
+
```javascript
|
|
257
|
+
import { VaultSandboxClient } from '@vaultsandbox/client';
|
|
258
|
+
|
|
259
|
+
const client = new VaultSandboxClient({ url, apiKey });
|
|
260
|
+
const inbox = await client.createInbox();
|
|
261
|
+
|
|
262
|
+
console.log(`Watching for emails at: ${inbox.emailAddress}`);
|
|
263
|
+
|
|
264
|
+
// Subscribe to new emails
|
|
265
|
+
const subscription = inbox.onNewEmail((email) => {
|
|
266
|
+
console.log(`New email received: "${email.subject}"`);
|
|
267
|
+
// Process the email here...
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// To stop listening for emails later:
|
|
271
|
+
// subscription.unsubscribe();
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## API Reference
|
|
275
|
+
|
|
276
|
+
### VaultSandboxClient
|
|
277
|
+
|
|
278
|
+
The main client class for interacting with the VaultSandbox Gateway.
|
|
279
|
+
|
|
280
|
+
#### Constructor
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
new VaultSandboxClient(config: ClientConfig)
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**ClientConfig:**
|
|
287
|
+
|
|
288
|
+
- `url: string` - Gateway URL
|
|
289
|
+
- `apiKey: string` - Your API key
|
|
290
|
+
- `strategy?: 'sse' | 'polling' | 'auto'` - Delivery strategy (default: 'auto')
|
|
291
|
+
- `pollingInterval?: number` - Polling interval in ms (default: 2000)
|
|
292
|
+
- `maxRetries?: number` - Max retry attempts for HTTP requests (default: 3)
|
|
293
|
+
- `retryDelay?: number` - Delay in ms between retry attempts (default: 1000)
|
|
294
|
+
- `retryOn?: number[]` - HTTP status codes that trigger a retry (default: [408, 429, 500, 502, 503, 504])
|
|
295
|
+
- `sseReconnectInterval?: number` - Initial delay in ms before SSE reconnection (default: 5000)
|
|
296
|
+
- `sseMaxReconnectAttempts?: number` - Max SSE reconnection attempts (default: 10)
|
|
297
|
+
|
|
298
|
+
#### Methods
|
|
299
|
+
|
|
300
|
+
- `createInbox(options?: CreateInboxOptions): Promise<Inbox>` - Creates a new inbox
|
|
301
|
+
- `deleteAllInboxes(): Promise<number>` - Deletes all inboxes for this API key
|
|
302
|
+
- `getServerInfo(): Promise<ServerInfo>` - Gets server information
|
|
303
|
+
- `checkKey(): Promise<boolean>` - Validates API key
|
|
304
|
+
- `monitorInboxes(inboxes: Inbox[]): InboxMonitor` - Monitors multiple inboxes and emits an `email` event when a new email arrives in any of them. Returns a monitor with an `unsubscribe()` method.
|
|
305
|
+
- `exportInbox(inboxOrEmail: Inbox | string): ExportedInboxData` - Exports an inbox's data for backup or sharing
|
|
306
|
+
- `importInbox(data: ExportedInboxData): Promise<Inbox>` - Imports an inbox from exported data
|
|
307
|
+
- `exportInboxToFile(inboxOrEmail: Inbox | string, filePath: string): void` - Exports an inbox to a JSON file
|
|
308
|
+
- `importInboxFromFile(filePath: string): Promise<Inbox>` - Imports an inbox from a JSON file
|
|
309
|
+
- `close(): Promise<void>` - Closes the client, terminates any active SSE or polling connections, and cleans up resources.
|
|
310
|
+
|
|
311
|
+
**Inbox Import/Export:** For advanced use cases like test reproducibility or sharing inboxes between environments, you can export an inbox (including its encryption keys) to a JSON file and import it later. This allows you to persist inboxes across test runs or share them with other tools.
|
|
312
|
+
|
|
313
|
+
**Testing with an Exported File:**
|
|
314
|
+
A manual test script is available at `tests/manual/check-inbox.manual-test.ts` to quickly test importing an inbox from a file and monitoring for new emails.
|
|
315
|
+
|
|
316
|
+
1. **Export Inbox:** From the VaultSandbox Web UI, export your inbox to a JSON file.
|
|
317
|
+
2. **Place File:** Create a `tmp` directory at the root of this project and place the exported file there (e.g., `tmp/my-inbox.json`).
|
|
318
|
+
3. **Update Script:** Open `tests/manual/check-inbox.manual-test.ts` and change the `jsonPath` variable to point to your file.
|
|
319
|
+
4. **Run Test:** Execute the script using `tsx`:
|
|
320
|
+
```bash
|
|
321
|
+
npx tsx tests/manual/check-inbox.manual-test.ts
|
|
322
|
+
```
|
|
323
|
+
The script will import the inbox and print a message whenever a new email is received.
|
|
324
|
+
|
|
325
|
+
### InboxMonitor
|
|
326
|
+
|
|
327
|
+
An event emitter for monitoring multiple inboxes simultaneously. Returned by `VaultSandboxClient.monitorInboxes()`.
|
|
328
|
+
|
|
329
|
+
#### Events
|
|
330
|
+
|
|
331
|
+
- `email(inbox: Inbox, email: Email)` - Emitted when a new email arrives in any monitored inbox
|
|
332
|
+
|
|
333
|
+
#### Methods
|
|
334
|
+
|
|
335
|
+
- `on(event: 'email', listener: (inbox: Inbox, email: Email) => void): this` - Subscribe to email events
|
|
336
|
+
- `unsubscribe(): void` - Unsubscribe from all inboxes and stop monitoring
|
|
337
|
+
|
|
338
|
+
#### Example
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
const inbox1 = await client.createInbox();
|
|
342
|
+
const inbox2 = await client.createInbox();
|
|
343
|
+
|
|
344
|
+
const monitor = client.monitorInboxes([inbox1, inbox2]);
|
|
345
|
+
|
|
346
|
+
console.log(`Monitoring inboxes: ${inbox1.emailAddress}, ${inbox2.emailAddress}`);
|
|
347
|
+
|
|
348
|
+
monitor.on('email', (inbox, email) => {
|
|
349
|
+
console.log(`New email in ${inbox.emailAddress}: ${email.subject}`);
|
|
350
|
+
// Further processing...
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// Later, to stop monitoring all inboxes:
|
|
354
|
+
// monitor.unsubscribe();
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Inbox
|
|
358
|
+
|
|
359
|
+
Represents a single email inbox.
|
|
360
|
+
|
|
361
|
+
#### Properties
|
|
362
|
+
|
|
363
|
+
- `emailAddress: string` - The inbox email address
|
|
364
|
+
- `inboxHash: string` - Unique inbox identifier
|
|
365
|
+
- `expiresAt: Date` - When the inbox expires
|
|
366
|
+
|
|
367
|
+
#### Methods
|
|
368
|
+
|
|
369
|
+
- `listEmails(): Promise<Email[]>` - Lists all emails (decrypted)
|
|
370
|
+
- `getEmail(emailId: string): Promise<Email>` - Gets a specific email
|
|
371
|
+
- `waitForEmail(options: WaitOptions): Promise<Email>` - Waits for an email matching criteria
|
|
372
|
+
- `waitForEmailCount(count: number, options?: WaitForCountOptions): Promise<void>` - Waits until the inbox has at least the specified number of emails. More efficient than using arbitrary timeouts in tests.
|
|
373
|
+
- `onNewEmail(callback: (email: Email) => void): Subscription` - Subscribes to new emails in real-time. Returns a subscription with an `unsubscribe()` method.
|
|
374
|
+
- `getSyncStatus(): Promise<SyncStatus>` - Gets inbox sync status
|
|
375
|
+
- `getRawEmail(emailId: string): Promise<RawEmail>` - Gets the raw, decrypted source of a specific email.
|
|
376
|
+
- `markEmailAsRead(emailId: string): Promise<void>` - Marks email as read
|
|
377
|
+
- `deleteEmail(emailId: string): Promise<void>` - Deletes an email
|
|
378
|
+
- `delete(): Promise<void>` - Deletes this inbox
|
|
379
|
+
- `export(): ExportedInboxData` - Exports inbox data and key material for backup/sharing (treat output as sensitive)
|
|
380
|
+
|
|
381
|
+
### Email
|
|
382
|
+
|
|
383
|
+
Represents a decrypted email.
|
|
384
|
+
|
|
385
|
+
#### Properties
|
|
386
|
+
|
|
387
|
+
- `id: string` - Email ID
|
|
388
|
+
- `from: string` - Sender address
|
|
389
|
+
- `to: string[]` - Recipient addresses
|
|
390
|
+
- `subject: string` - Email subject
|
|
391
|
+
- `text: string | null` - Plain text content
|
|
392
|
+
- `html: string | null` - HTML content
|
|
393
|
+
- `receivedAt: Date` - When the email was received
|
|
394
|
+
- `isRead: boolean` - Read status
|
|
395
|
+
- `links: string[]` - Extracted URLs from email
|
|
396
|
+
- `headers: Record<string, unknown>` - All email headers
|
|
397
|
+
- `attachments: AttachmentData[]` - Email attachments
|
|
398
|
+
- `authResults: AuthResults` - Email authentication results
|
|
399
|
+
- `metadata: Record<string, unknown>` - Other metadata associated with the email
|
|
400
|
+
|
|
401
|
+
#### Methods
|
|
402
|
+
|
|
403
|
+
- `markAsRead(): Promise<void>` - Marks this email as read
|
|
404
|
+
- `delete(): Promise<void>` - Deletes this email
|
|
405
|
+
- `getRaw(): Promise<RawEmail>` - Gets raw email source
|
|
406
|
+
|
|
407
|
+
### AuthResults
|
|
408
|
+
|
|
409
|
+
Returned by `email.authResults`, this object contains email authentication results (SPF, DKIM, DMARC) and a validation helper.
|
|
410
|
+
|
|
411
|
+
#### Properties
|
|
412
|
+
|
|
413
|
+
- `spf?: SPFResult` - SPF result
|
|
414
|
+
- `dkim?: DKIMResult[]` - All DKIM results
|
|
415
|
+
- `dmarc?: DMARCResult` - DMARC result
|
|
416
|
+
- `reverseDns?: ReverseDNSResult` - Reverse DNS result
|
|
417
|
+
|
|
418
|
+
#### Methods
|
|
419
|
+
|
|
420
|
+
- `validate(): AuthValidation` - Validates all authentication results and returns a summary object with `passed`, per-check booleans (`spfPassed`, `dkimPassed`, `dmarcPassed`, `reverseDnsPassed`), and a list of `failures`.
|
|
421
|
+
|
|
422
|
+
### CreateInboxOptions
|
|
423
|
+
|
|
424
|
+
Options for creating an inbox with `client.createInbox()`.
|
|
425
|
+
|
|
426
|
+
**Properties:**
|
|
427
|
+
|
|
428
|
+
- `ttl?: number` - Time-to-live for the inbox in seconds (default: server-defined).
|
|
429
|
+
- `emailAddress?: string` - A specific email address to request (e.g., `test@inbox.vaultsandbox.com`). If unavailable, the server will generate one.
|
|
430
|
+
|
|
431
|
+
### WaitOptions
|
|
432
|
+
|
|
433
|
+
Options for waiting for emails with `inbox.waitForEmail()`.
|
|
434
|
+
|
|
435
|
+
**Properties:**
|
|
436
|
+
|
|
437
|
+
- `timeout?: number` - Maximum time to wait in milliseconds (default: 30000)
|
|
438
|
+
- `pollInterval?: number` - Polling interval in milliseconds (default: 2000)
|
|
439
|
+
- `subject?: string | RegExp` - Filter emails by subject
|
|
440
|
+
- `from?: string | RegExp` - Filter emails by sender address
|
|
441
|
+
- `predicate?: (email: Email) => boolean` - Custom filter function
|
|
442
|
+
|
|
443
|
+
**Example:**
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
// Wait for email with specific subject
|
|
447
|
+
const email = await inbox.waitForEmail({
|
|
448
|
+
timeout: 10000,
|
|
449
|
+
subject: /Password Reset/,
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// Wait with custom predicate
|
|
453
|
+
const email = await inbox.waitForEmail({
|
|
454
|
+
timeout: 15000,
|
|
455
|
+
predicate: (email) => email.to.includes('user@example.com'),
|
|
456
|
+
});
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### WaitForCountOptions
|
|
460
|
+
|
|
461
|
+
Options for waiting for a specific number of emails with `inbox.waitForEmailCount()`.
|
|
462
|
+
|
|
463
|
+
**Properties:**
|
|
464
|
+
|
|
465
|
+
- `timeout?: number` - Maximum time to wait in milliseconds (default: 30000)
|
|
466
|
+
- `pollInterval?: number` - Polling interval in milliseconds (default: 1000)
|
|
467
|
+
|
|
468
|
+
**Example:**
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
// Trigger multiple emails
|
|
472
|
+
await sendMultipleNotifications(inbox.emailAddress);
|
|
473
|
+
|
|
474
|
+
// Wait for all 3 to arrive
|
|
475
|
+
await inbox.waitForEmailCount(3, { timeout: 30000 });
|
|
476
|
+
|
|
477
|
+
const emails = await inbox.listEmails();
|
|
478
|
+
expect(emails.length).toBe(3);
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## Error Handling
|
|
482
|
+
|
|
483
|
+
The SDK is designed to be resilient and provide clear feedback when issues occur. It includes automatic retries for transient network and server errors, and throws specific, catchable errors for different failure scenarios.
|
|
484
|
+
|
|
485
|
+
All custom errors thrown by the SDK extend from the base `VaultSandboxError` class, so you can catch all SDK-specific errors with a single `catch` block if needed.
|
|
486
|
+
|
|
487
|
+
### Automatic Retries
|
|
488
|
+
|
|
489
|
+
By default, the client automatically retries failed HTTP requests that result in one of the following status codes: `408`, `429`, `500`, `502`, `503`, `504`. This helps mitigate transient network or server-side issues.
|
|
490
|
+
|
|
491
|
+
The retry behavior can be configured via the `VaultSandboxClient` constructor:
|
|
492
|
+
|
|
493
|
+
- `maxRetries`: The maximum number of retry attempts (default: `3`).
|
|
494
|
+
- `retryDelay`: The base delay in milliseconds between retries (default: `1000`). The delay uses exponential backoff.
|
|
495
|
+
- `retryOn`: An array of HTTP status codes that should trigger a retry.
|
|
496
|
+
|
|
497
|
+
### Custom Error Types
|
|
498
|
+
|
|
499
|
+
The following custom error classes may be thrown:
|
|
500
|
+
|
|
501
|
+
- **`ApiError`**: Thrown for API-level errors (e.g., invalid request, permission denied). Includes a `statusCode` property.
|
|
502
|
+
- **`NetworkError`**: Thrown when there is a network-level failure (e.g., the client cannot connect to the server).
|
|
503
|
+
- **`TimeoutError`**: Thrown by methods like `waitForEmail` and `waitForEmailCount` when the timeout is reached before the condition is met.
|
|
504
|
+
- **`InboxNotFoundError`**: Thrown when an operation targets an inbox that does not exist (HTTP 404).
|
|
505
|
+
- **`EmailNotFoundError`**: Thrown when an operation targets an email that does not exist (HTTP 404).
|
|
506
|
+
- **`InboxAlreadyExistsError`**: Thrown when attempting to import an inbox that already exists in the client.
|
|
507
|
+
- **`InvalidImportDataError`**: Thrown when imported inbox data fails validation (missing fields, invalid keys, server mismatch, etc.).
|
|
508
|
+
- **`DecryptionError`**: Thrown if the client fails to decrypt an email. This is rare and may indicate data corruption or a bug.
|
|
509
|
+
- **`SignatureVerificationError`**: Thrown if the cryptographic signature of a message from the server cannot be verified. This is a critical error that may indicate a man-in-the-middle (MITM) attack.
|
|
510
|
+
- **`SSEError`**: Thrown for errors related to the Server-Sent Events (SSE) connection.
|
|
511
|
+
|
|
512
|
+
### Example
|
|
513
|
+
|
|
514
|
+
You can use a `try...catch` block to handle errors and use `instanceof` to check for specific error types.
|
|
515
|
+
|
|
516
|
+
```javascript
|
|
517
|
+
import { VaultSandboxClient, ApiError, TimeoutError, VaultSandboxError } from '@vaultsandbox/client';
|
|
518
|
+
|
|
519
|
+
const client = new VaultSandboxClient({ url, apiKey });
|
|
520
|
+
|
|
521
|
+
try {
|
|
522
|
+
const inbox = await client.createInbox();
|
|
523
|
+
console.log(`Send email to: ${inbox.emailAddress}`);
|
|
524
|
+
|
|
525
|
+
// This might throw a TimeoutError
|
|
526
|
+
const email = await inbox.waitForEmail({ timeout: 5000 });
|
|
527
|
+
|
|
528
|
+
console.log('Email received:', email.subject);
|
|
529
|
+
await inbox.delete();
|
|
530
|
+
} catch (error) {
|
|
531
|
+
if (error instanceof TimeoutError) {
|
|
532
|
+
console.error('Timed out waiting for email:', error.message);
|
|
533
|
+
} else if (error instanceof ApiError) {
|
|
534
|
+
console.error(`API Error (${error.statusCode}):`, error.message);
|
|
535
|
+
} else if (error instanceof VaultSandboxError) {
|
|
536
|
+
// Catch any other SDK-specific error
|
|
537
|
+
console.error('An unexpected SDK error occurred:', error.message);
|
|
538
|
+
} else {
|
|
539
|
+
// Handle other unexpected errors
|
|
540
|
+
console.error('An unexpected error occurred:', error);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
## Requirements
|
|
546
|
+
|
|
547
|
+
- Node.js >= 20.0.0 (tested on Node 20 and 22; ES2022 target)
|
|
548
|
+
- Not supported in browsers/edge runtimes
|
|
549
|
+
- VaultSandbox Gateway server
|
|
550
|
+
- Valid API key
|
|
551
|
+
|
|
552
|
+
## Testing
|
|
553
|
+
|
|
554
|
+
```bash
|
|
555
|
+
# Run unit tests
|
|
556
|
+
npm test
|
|
557
|
+
|
|
558
|
+
# Run tests in watch mode
|
|
559
|
+
npm run test:watch
|
|
560
|
+
|
|
561
|
+
# Run tests with coverage
|
|
562
|
+
npm run test:cov
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
## Building
|
|
566
|
+
|
|
567
|
+
```bash
|
|
568
|
+
# Build TypeScript
|
|
569
|
+
npm run build
|
|
570
|
+
|
|
571
|
+
# Clean build artifacts
|
|
572
|
+
npm run clean
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
## Architecture
|
|
576
|
+
|
|
577
|
+
The SDK is built on several layers:
|
|
578
|
+
|
|
579
|
+
1. **Crypto Layer**: Handles ML-KEM-768 keypair generation, AES-256-GCM encryption/decryption, and ML-DSA-65 signature verification
|
|
580
|
+
2. **HTTP Layer**: REST API client with automatic retry and error handling
|
|
581
|
+
3. **Domain Layer**: Email, Inbox, and Client classes with intuitive APIs
|
|
582
|
+
4. **Strategy Layer**: SSE and polling strategies for email delivery
|
|
583
|
+
|
|
584
|
+
All cryptographic operations are performed transparently - developers never need to handle keys, encryption, or signatures directly.
|
|
585
|
+
|
|
586
|
+
## Security
|
|
587
|
+
|
|
588
|
+
- Cryptography: ML-KEM-768 (Kyber768) for key encapsulation + AES-256-GCM for payload encryption, with HKDF-SHA-512 key derivation.
|
|
589
|
+
- Signatures: ML-DSA-65 (Dilithium3) signatures are verified **before** any decryption using the gateway-provided transcript context (`vaultsandbox:email:v1` today).
|
|
590
|
+
- Threat model: protects confidentiality/integrity of gateway responses and detects tampering/mitm. Skipping signature verification defeats these guarantees.
|
|
591
|
+
- Key handling: inbox keypairs stay in memory only; exported inbox data contains secrets and must be treated as sensitive.
|
|
592
|
+
- Validation: signature verification failures throw `SignatureVerificationError`; decryption issues throw `DecryptionError`. Always surface these in logs/alerts for investigation.
|
|
593
|
+
|
|
594
|
+
## Related
|
|
595
|
+
|
|
596
|
+
- [VaultSandbox Gateway](https://github.com/vaultsandbox/gateway) — The self-hosted SMTP server this SDK connects to
|
|
597
|
+
- [VaultSandbox Documentation](https://vaultsandbox.dev) — Full documentation and guides
|
|
598
|
+
|
|
599
|
+
## Support
|
|
600
|
+
|
|
601
|
+
- [Documentation](https://vaultsandbox.dev/client-node/installation)
|
|
602
|
+
- [Issue Tracker](https://github.com/vaultsandbox/client-node/issues)
|
|
603
|
+
- [Discussions](https://github.com/vaultsandbox/client-node/discussions)
|
|
604
|
+
- [Website](https://www.vaultsandbox.com)
|
|
605
|
+
|
|
606
|
+
## Contributing
|
|
607
|
+
|
|
608
|
+
Contributions are welcome! Please read our [contributing guidelines](CONTRIBUTING.md) before submitting PRs.
|
|
609
|
+
|
|
610
|
+
## License
|
|
611
|
+
|
|
612
|
+
MIT — see [LICENSE](LICENSE) for details.
|