@bernierllc/email-service 4.0.1 → 4.0.3

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.
@@ -178,19 +178,26 @@ export class EmailService {
178
178
  }
179
179
  // Create delivery record
180
180
  const deliveryId = this.generateId();
181
- const deliveryRecord = {
181
+ const deliveryData = {
182
182
  id: deliveryId,
183
- to: Array.isArray(request.to) ? request.to[0].toString() : request.to,
184
- from: typeof request.from === 'string' ? request.from : request.from.email,
183
+ message_id: '',
184
+ to_address: Array.isArray(request.to) ? request.to[0].toString() : request.to,
185
+ from_address: typeof request.from === 'string' ? request.from : request.from.email,
185
186
  subject,
186
187
  provider,
187
188
  status: DeliveryStatus.SENDING,
188
- templateId: request.templateId,
189
- metadata: request.metadata,
190
- createdAt: new Date(),
191
- updatedAt: new Date()
189
+ template_id: request.templateId || null,
190
+ metadata: request.metadata ? JSON.stringify(request.metadata) : null,
191
+ sent_at: null,
192
+ delivered_at: null,
193
+ opened_at: null,
194
+ clicked_at: null,
195
+ bounced_at: null,
196
+ error: null,
197
+ created_at: new Date().toISOString(),
198
+ updated_at: new Date().toISOString()
192
199
  };
193
- await this.db.insert('email_deliveries', deliveryRecord);
200
+ await this.db.insert('email_deliveries', deliveryData);
194
201
  // Send email with retry logic
195
202
  const toEmail = Array.isArray(request.to)
196
203
  ? (typeof request.to[0] === 'string' ? request.to[0] : request.to[0].email)
@@ -211,11 +218,11 @@ export class EmailService {
211
218
  const result = await this.sendWithRetry(sender, emailMessage, deliveryId, provider);
212
219
  // Update delivery record
213
220
  await this.db.update('email_deliveries', deliveryId, {
214
- messageId: result.messageId,
221
+ message_id: result.messageId || '',
215
222
  status: result.success ? DeliveryStatus.SENT : DeliveryStatus.FAILED,
216
- sentAt: result.success ? new Date() : undefined,
217
- error: result.errorMessage,
218
- updatedAt: new Date()
223
+ sent_at: result.success ? new Date().toISOString() : null,
224
+ error: result.errorMessage || null,
225
+ updated_at: new Date().toISOString()
219
226
  });
220
227
  this.logger.info(`Email sent via ${provider}`, { deliveryId, messageId: result.messageId });
221
228
  return {
@@ -274,8 +281,9 @@ export class EmailService {
274
281
  try {
275
282
  const id = this.generateId();
276
283
  const now = new Date();
277
- // Extract variables from template
278
- const variables = this.extractVariables(request.html);
284
+ // Extract variables from all template parts
285
+ const allContent = [request.subject, request.html, request.text].filter(Boolean).join(' ');
286
+ const variables = this.extractVariables(allContent);
279
287
  const template = {
280
288
  id,
281
289
  name: request.name,
@@ -288,8 +296,15 @@ export class EmailService {
288
296
  updatedAt: now
289
297
  };
290
298
  await this.db.insert('email_templates', {
291
- ...template,
292
- variables: JSON.stringify(variables)
299
+ id,
300
+ name: request.name,
301
+ subject: request.subject,
302
+ html: request.html,
303
+ text: request.text || null,
304
+ variables: JSON.stringify(variables),
305
+ version: 1,
306
+ created_at: now.toISOString(),
307
+ updated_at: now.toISOString()
293
308
  });
294
309
  if (this.config.templates?.cacheEnabled) {
295
310
  this.templateCache.set(id, template);
@@ -329,14 +344,17 @@ export class EmailService {
329
344
  throw new TemplateError(`Template ${id} not found`);
330
345
  }
331
346
  const html = update.html || existing.html;
332
- const variables = this.extractVariables(html);
347
+ const subject = update.subject || existing.subject;
348
+ const text = update.text || existing.text;
349
+ const allContent = [subject, html, text].filter(Boolean).join(' ');
350
+ const variables = this.extractVariables(allContent);
333
351
  const updated = await this.db.update('email_templates', id, {
334
352
  subject: update.subject || existing.subject,
335
353
  html,
336
354
  text: update.text || existing.text,
337
355
  variables: JSON.stringify(variables),
338
356
  version: existing.version + 1,
339
- updatedAt: new Date()
357
+ updated_at: new Date().toISOString()
340
358
  });
341
359
  // Invalidate cache
342
360
  this.templateCache.delete(id);
@@ -388,15 +406,15 @@ export class EmailService {
388
406
  async trackOpen(deliveryId) {
389
407
  await this.db.update('email_deliveries', deliveryId, {
390
408
  status: DeliveryStatus.OPENED,
391
- openedAt: new Date(),
392
- updatedAt: new Date()
409
+ opened_at: new Date().toISOString(),
410
+ updated_at: new Date().toISOString()
393
411
  });
394
412
  }
395
413
  async trackClick(deliveryId) {
396
414
  await this.db.update('email_deliveries', deliveryId, {
397
415
  status: DeliveryStatus.CLICKED,
398
- clickedAt: new Date(),
399
- updatedAt: new Date()
416
+ clicked_at: new Date().toISOString(),
417
+ updated_at: new Date().toISOString()
400
418
  });
401
419
  }
402
420
  async getDeliveryStats(startDate, endDate) {
@@ -457,9 +475,9 @@ export class EmailService {
457
475
  lists: JSON.stringify(request.lists || []),
458
476
  tags: request.tags ? JSON.stringify(request.tags) : null,
459
477
  metadata: request.metadata ? JSON.stringify(request.metadata) : null,
460
- subscribedAt: now,
461
- createdAt: now,
462
- updatedAt: now
478
+ subscribed_at: now.toISOString(),
479
+ created_at: now.toISOString(),
480
+ updated_at: now.toISOString()
463
481
  });
464
482
  this.logger.info(`Subscriber created: ${request.email}`, { id });
465
483
  return this.parseSubscriber(subscriber);
@@ -470,7 +488,7 @@ export class EmailService {
470
488
  }
471
489
  async updateSubscriber(id, update) {
472
490
  const updateData = {
473
- updatedAt: new Date()
491
+ updated_at: new Date().toISOString()
474
492
  };
475
493
  if (update.name !== undefined)
476
494
  updateData.name = update.name;
@@ -492,8 +510,8 @@ export class EmailService {
492
510
  }
493
511
  await this.db.update('subscribers', subscriber.id, {
494
512
  status: 'unsubscribed',
495
- unsubscribedAt: new Date(),
496
- updatedAt: new Date()
513
+ unsubscribed_at: new Date().toISOString(),
514
+ updated_at: new Date().toISOString()
497
515
  });
498
516
  this.logger.info(`Subscriber unsubscribed: ${email}`);
499
517
  return true;
@@ -513,14 +531,22 @@ export class EmailService {
513
531
  async createList(name, description) {
514
532
  const id = this.generateId();
515
533
  const now = new Date();
516
- return await this.db.insert('subscriber_lists', {
534
+ const record = await this.db.insert('subscriber_lists', {
517
535
  id,
518
536
  name,
519
537
  description,
520
- subscriberCount: 0,
521
- createdAt: now,
522
- updatedAt: now
538
+ subscriber_count: 0,
539
+ created_at: now.toISOString(),
540
+ updated_at: now.toISOString()
523
541
  });
542
+ return {
543
+ id: record.id,
544
+ name: record.name,
545
+ description: record.description,
546
+ subscriberCount: record.subscriber_count,
547
+ createdAt: new Date(record.created_at),
548
+ updatedAt: new Date(record.updated_at)
549
+ };
524
550
  }
525
551
  // ===== Webhooks =====
526
552
  registerWebhookHandler(event, handler) {
@@ -610,7 +636,21 @@ export class EmailService {
610
636
  ...config.customData
611
637
  }
612
638
  };
613
- const emailResult = await this.send(emailRequest);
639
+ let emailResult;
640
+ try {
641
+ emailResult = await this.send(emailRequest);
642
+ }
643
+ catch (sendError) {
644
+ const sendErr = sendError instanceof Error ? sendError : new Error(String(sendError));
645
+ this.logger.error('Failed to send magic link email', sendErr);
646
+ return {
647
+ success: false,
648
+ magicLink,
649
+ token,
650
+ expiresAt,
651
+ error: sendErr.message
652
+ };
653
+ }
614
654
  this.logger.info(`Magic link email sent`, {
615
655
  recipient: config.recipient,
616
656
  purpose: config.purpose,
@@ -627,7 +667,7 @@ export class EmailService {
627
667
  }
628
668
  catch (error) {
629
669
  const err = error instanceof Error ? error : new Error(String(error));
630
- this.logger.error('Failed to send magic link email', err);
670
+ this.logger.error('Failed to generate magic link', err);
631
671
  return {
632
672
  success: false,
633
673
  error: err.message
package/dist/types.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { DatabaseConfig } from '@bernierllc/database-adapter';
2
2
  import type { QueueOptions } from '@bernierllc/queue-manager';
3
3
  import type { TemplateContext } from '@bernierllc/template-engine';
4
- export type EmailProvider = 'sendgrid' | 'mailgun' | 'ses' | 'smtp';
4
+ export type EmailProvider = 'sendgrid' | 'mailgun' | 'ses' | 'smtp' | 'postmark' | 'mandrill' | 'smtp2go';
5
5
  export interface ProviderConfig {
6
6
  type: EmailProvider;
7
7
  apiKey?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bernierllc/email-service",
3
- "version": "4.0.1",
3
+ "version": "4.0.3",
4
4
  "description": "Comprehensive email service orchestrating template management, multi-provider delivery, tracking, and subscriber management",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -26,19 +26,19 @@
26
26
  "author": "Bernier LLC",
27
27
  "license": "PROPRIETARY",
28
28
  "dependencies": {
29
- "@bernierllc/email-sender": "5.1.1",
30
- "@bernierllc/database-adapter": "1.2.1",
31
- "@bernierllc/logger": "1.3.1",
32
- "@bernierllc/queue-manager": "1.2.1",
33
- "@bernierllc/retry-metrics": "0.2.1",
34
- "@bernierllc/retry-policy": "0.3.1",
35
- "@bernierllc/retry-state": "0.2.1",
36
- "@bernierllc/webhook-validator": "1.0.4",
37
- "@bernierllc/template-engine": "0.4.1",
38
- "@bernierllc/email-parser": "0.4.0",
39
- "@bernierllc/email-validator": "3.0.1",
40
- "@bernierllc/config-manager": "1.0.6",
41
- "@bernierllc/crypto-utils": "1.0.7"
29
+ "@bernierllc/database-adapter": "1.2.2",
30
+ "@bernierllc/email-parser": "0.4.1",
31
+ "@bernierllc/email-sender": "5.2.1",
32
+ "@bernierllc/email-validator": "3.0.2",
33
+ "@bernierllc/logger": "1.3.2",
34
+ "@bernierllc/queue-manager": "1.2.2",
35
+ "@bernierllc/retry-metrics": "0.2.2",
36
+ "@bernierllc/retry-policy": "0.3.2",
37
+ "@bernierllc/retry-state": "0.2.2",
38
+ "@bernierllc/template-engine": "0.4.2",
39
+ "@bernierllc/config-manager": "1.0.7",
40
+ "@bernierllc/webhook-validator": "1.0.5",
41
+ "@bernierllc/crypto-utils": "1.0.8"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "@sendgrid/mail": "^8.0.0",
@@ -80,7 +80,7 @@
80
80
  "prebuild": "npm run clean",
81
81
  "clean": "rm -rf dist",
82
82
  "test": "jest",
83
- "test:run": "jest --watchAll=false",
83
+ "test:run": "jest --watchAll=false --forceExit",
84
84
  "test:watch": "jest --watch",
85
85
  "test:coverage": "jest --coverage",
86
86
  "lint": "eslint src --ext .ts"