@mailhooks/sdk 2.4.0 → 2.6.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 CHANGED
@@ -161,6 +161,115 @@ const email = await mailhooks.emails.waitFor({
161
161
  - Efficiently tracks checked emails to avoid duplicates
162
162
  - Throws error on timeout or max retries exceeded
163
163
 
164
+ ## Push Notifications (SSE)
165
+
166
+ Get instant push notifications when emails arrive using Server-Sent Events. This is ideal for serverless environments, client-side apps, firewalled networks, or local development where webhooks aren't practical.
167
+
168
+ ### Basic Usage
169
+
170
+ ```typescript
171
+ import { Mailhooks } from '@mailhooks/sdk';
172
+
173
+ const mailhooks = new Mailhooks({ apiKey: 'mh_xxx' });
174
+
175
+ const subscription = mailhooks.realtime.subscribe({
176
+ onEmailReceived: (email) => {
177
+ console.log('New email:', email.subject);
178
+ console.log('From:', email.from);
179
+ },
180
+ onConnected: (payload) => {
181
+ console.log('Connected!', payload.connectionId);
182
+ },
183
+ onError: (error) => {
184
+ console.error('Error:', error);
185
+ },
186
+ });
187
+
188
+ // Later, disconnect when done
189
+ subscription.disconnect();
190
+ ```
191
+
192
+ ### Distributed Mode (Worker Load Balancing)
193
+
194
+ When running multiple workers, use distributed mode so only ONE worker receives each notification:
195
+
196
+ ```typescript
197
+ // Each worker connects with the same API key
198
+ // Only ONE worker receives each notification (random selection)
199
+ const subscription = mailhooks.realtime.subscribe({
200
+ mode: 'distributed',
201
+ onEmailReceived: async (email) => {
202
+ // Process email - only one worker will receive this
203
+ await processEmail(email.id);
204
+ },
205
+ });
206
+ ```
207
+
208
+ ### Full Event Handling
209
+
210
+ ```typescript
211
+ const subscription = mailhooks.realtime.subscribe({
212
+ mode: 'broadcast', // or 'distributed'
213
+ onConnected: (payload) => {
214
+ console.log(`Connected: ${payload.connectionId}`);
215
+ console.log(`Mode: ${payload.mode}`);
216
+ },
217
+ onEmailReceived: (email) => {
218
+ console.log(`From: ${email.from}`);
219
+ console.log(`Subject: ${email.subject}`);
220
+ console.log(`Has attachments: ${email.hasAttachments}`);
221
+ },
222
+ onEmailUpdated: (update) => {
223
+ console.log(`Email ${update.id} updated:`, update.changes);
224
+ },
225
+ onHeartbeat: () => {
226
+ // Sent every 30 seconds to keep connection alive
227
+ },
228
+ onError: (error) => {
229
+ console.error('Connection error:', error);
230
+ },
231
+ onDisconnect: () => {
232
+ console.log('Disconnected');
233
+ },
234
+ autoReconnect: true, // Automatically reconnect on disconnect (default: true)
235
+ reconnectDelay: 5000, // Delay between reconnect attempts in ms (default: 5000)
236
+ });
237
+ ```
238
+
239
+ ### Node.js Usage
240
+
241
+ In Node.js, you need to install the `eventsource` package:
242
+
243
+ ```bash
244
+ npm install eventsource
245
+ ```
246
+
247
+ Then make EventSource available globally before importing the SDK:
248
+
249
+ ```typescript
250
+ import EventSource from 'eventsource';
251
+ (global as any).EventSource = EventSource;
252
+
253
+ import { Mailhooks } from '@mailhooks/sdk';
254
+ // Use as normal
255
+ ```
256
+
257
+ ### Event Types
258
+
259
+ | Event | Payload | Description |
260
+ |-------|---------|-------------|
261
+ | `connected` | `{ tenantId, connectedAt, mode, connectionId }` | Connection established |
262
+ | `email.received` | `{ id, from, to, subject, hasAttachments, ... }` | New email arrived |
263
+ | `email.updated` | `{ id, changes: { read?: boolean } }` | Email was updated |
264
+ | `heartbeat` | `{ timestamp }` | Keep-alive ping (every 30s) |
265
+
266
+ ### Connection Modes
267
+
268
+ | Mode | Behavior | Use Case |
269
+ |------|----------|----------|
270
+ | `broadcast` | All connected clients receive every event | Dashboards, monitoring |
271
+ | `distributed` | One random client per API key receives each event | Worker pools, load balancing |
272
+
164
273
  ## Types
165
274
 
166
275
  The SDK includes comprehensive TypeScript types:
package/dist/index.d.ts CHANGED
@@ -153,7 +153,13 @@ declare class EmailsResource extends MailhooksClient {
153
153
  }
154
154
 
155
155
  /**
156
- * Event types for real-time notifications
156
+ * Connection mode for SSE subscriptions
157
+ * - broadcast: All connected clients receive every event (default)
158
+ * - distributed: Only ONE client per API key group receives each event (load balancing)
159
+ */
160
+ type ConnectionMode = 'broadcast' | 'distributed';
161
+ /**
162
+ * Event types for push notifications
157
163
  */
158
164
  declare enum RealtimeEventType {
159
165
  EMAIL_RECEIVED = "email.received",
@@ -190,6 +196,8 @@ interface EmailUpdatedPayload {
190
196
  interface ConnectedPayload {
191
197
  tenantId: string;
192
198
  connectedAt: string;
199
+ mode?: ConnectionMode;
200
+ connectionId?: string;
193
201
  }
194
202
  /**
195
203
  * Heartbeat event payload
@@ -220,6 +228,12 @@ interface RealtimeCallbacks {
220
228
  * Subscription options
221
229
  */
222
230
  interface SubscribeOptions extends RealtimeCallbacks {
231
+ /**
232
+ * Connection mode (default: 'broadcast')
233
+ * - broadcast: All connected clients receive every event
234
+ * - distributed: Only ONE client per API key group receives each event (load balancing for workers)
235
+ */
236
+ mode?: ConnectionMode;
223
237
  /** Auto-reconnect on disconnect (default: true) */
224
238
  autoReconnect?: boolean;
225
239
  /** Reconnect delay in milliseconds (default: 5000) */
@@ -260,16 +274,6 @@ declare class RealtimeResource {
260
274
  private eventSource;
261
275
  private reconnectTimeout;
262
276
  constructor(config: MailhooksConfig);
263
- /**
264
- * Check if the plan allows real-time notifications and get connection quota
265
- *
266
- * @returns Promise resolving to access status and connection limits
267
- */
268
- checkAccess(): Promise<{
269
- hasAccess: boolean;
270
- maxConnections: number;
271
- currentConnections: number;
272
- }>;
273
277
  /**
274
278
  * Subscribe to real-time email notifications via Server-Sent Events (SSE)
275
279
  *
@@ -331,6 +335,8 @@ interface WebhookPayload {
331
335
  html?: string;
332
336
  /** Array of attachment metadata */
333
337
  attachments: Array<{
338
+ /** Unique attachment ID */
339
+ id: string;
334
340
  filename: string;
335
341
  contentType: string;
336
342
  size: number;
package/dist/index.js CHANGED
@@ -256,23 +256,6 @@ var RealtimeResource = class {
256
256
  this.reconnectTimeout = null;
257
257
  this.config = config;
258
258
  }
259
- /**
260
- * Check if the plan allows real-time notifications and get connection quota
261
- *
262
- * @returns Promise resolving to access status and connection limits
263
- */
264
- async checkAccess() {
265
- const baseUrl = this.config.baseUrl ?? "https://mailhooks.dev/api";
266
- const response = await fetch(`${baseUrl}/v1/realtime/plan-access`, {
267
- headers: {
268
- "x-api-key": this.config.apiKey
269
- }
270
- });
271
- if (!response.ok) {
272
- throw new Error(`Failed to check plan access: ${response.statusText}`);
273
- }
274
- return response.json();
275
- }
276
259
  /**
277
260
  * Subscribe to real-time email notifications via Server-Sent Events (SSE)
278
261
  *
@@ -305,6 +288,7 @@ var RealtimeResource = class {
305
288
  */
306
289
  subscribe(options = {}) {
307
290
  const {
291
+ mode = "broadcast",
308
292
  onEmailReceived,
309
293
  onEmailUpdated,
310
294
  onConnected,
@@ -328,7 +312,7 @@ var RealtimeResource = class {
328
312
  );
329
313
  }
330
314
  const baseUrl = this.config.baseUrl ?? "https://mailhooks.dev/api";
331
- const url = `${baseUrl}/v1/realtime/events?token=${encodeURIComponent(this.config.apiKey)}`;
315
+ const url = `${baseUrl}/v1/realtime/events?token=${encodeURIComponent(this.config.apiKey)}&mode=${mode}`;
332
316
  const connect = () => {
333
317
  this.eventSource = new EventSource(url);
334
318
  this.eventSource.onmessage = (event) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mailhooks/sdk",
3
- "version": "2.4.0",
3
+ "version": "2.6.0",
4
4
  "description": "TypeScript SDK for Mailhooks API",
5
5
  "publishConfig": {
6
6
  "access": "public"