@xcelsior/email 1.0.1 → 1.0.2

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/dist/index.d.mts CHANGED
@@ -11,7 +11,7 @@ interface Email {
11
11
  attachments?: any[];
12
12
  subject: string;
13
13
  }
14
- declare const emailTransporter: (sourceArn: string | undefined, logger?: any) => {
14
+ declare const emailTransporter: (logger?: any) => {
15
15
  sendMail({ to, reference, from, replyTo, template, text, attachments, idempotencyKey, subject, }: Email, idempotentCheck?: boolean): Promise<nodemailer_lib_smtp_pool.SentMessageInfo | undefined>;
16
16
  };
17
17
 
package/dist/index.d.ts CHANGED
@@ -11,7 +11,7 @@ interface Email {
11
11
  attachments?: any[];
12
12
  subject: string;
13
13
  }
14
- declare const emailTransporter: (sourceArn: string | undefined, logger?: any) => {
14
+ declare const emailTransporter: (logger?: any) => {
15
15
  sendMail({ to, reference, from, replyTo, template, text, attachments, idempotencyKey, subject, }: Email, idempotentCheck?: boolean): Promise<nodemailer_lib_smtp_pool.SentMessageInfo | undefined>;
16
16
  };
17
17
 
package/dist/index.js CHANGED
@@ -108,22 +108,28 @@ var EmailLogService = class {
108
108
  var sesClient = new import_client_sesv2.SESv2Client({
109
109
  region: process.env.AWS_REGION
110
110
  });
111
- var emailOptions = process.env.NODE_ENV === "development" ? {
112
- port: 1025,
113
- host: process.env.SMTP_HOST
111
+ var emailOptions = process.env.EMAIL_TRANSPORT === "smtp" ? {
112
+ port: process.env.SMTP_PORT ? parseInt(process.env.SMTP_PORT, 10) : 587,
113
+ host: process.env.SMTP_HOST,
114
+ auth: process.env.SMTP_USER && process.env.SMTP_PASSWORD ? {
115
+ user: process.env.SMTP_USER,
116
+ pass: process.env.SMTP_PASSWORD
117
+ } : void 0
114
118
  } : {
115
119
  SES: { sesClient, SendEmailCommand: import_client_sesv2.SendEmailCommand }
116
120
  };
117
- var emailTransporter = (sourceArn, logger = console) => {
121
+ var emailTransporter = (logger = console) => {
118
122
  const transporter = import_nodemailer.default.createTransport(
119
123
  {
120
124
  ...emailOptions,
121
125
  debug: true
122
126
  },
123
127
  {
124
- ses: {
125
- FromEmailAddressIdentityArn: sourceArn
126
- }
128
+ ...process.env.EMAIL_SOURCE_ARN ? {
129
+ ses: {
130
+ FromEmailAddressIdentityArn: process.env.EMAIL_SOURCE_ARN
131
+ }
132
+ } : void 0
127
133
  }
128
134
  );
129
135
  return {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/email.ts","../src/email-log.ts","../src/template.ts"],"sourcesContent":["export * from './email';\nexport * from './template';\n","import { SendEmailCommand, SESv2Client } from '@aws-sdk/client-sesv2';\nimport nodemailer from 'nodemailer';\nimport { EmailLogService } from './email-log';\n\n/**\n * Template to use\n * await emailTransporter().sendMail({\n from: 'AusVie noreply<noreply@ausvie.com.au>',\n to: 'admin@ausvie.com.au',\n subject: 'Test', // Subject line\n text: 'Test text', // plaintext version\n html: `<div>${'Test text'}</div>`, // html version\n // attachments: [\n // {\n // filename,\n // content: fileData,\n // },\n // ],\n });\n */\n// Load the AWS SDK for Node.js\n// https://stackoverflow.com/questions/23042835/sending-mail-via-aws-ses-with-attachment-in-node-js\n\nconst sesClient = new SESv2Client({\n region: process.env.AWS_REGION,\n});\n\nconst emailOptions =\n process.env.NODE_ENV === 'development'\n ? {\n port: 1025,\n host: process.env.SMTP_HOST,\n }\n : {\n SES: { sesClient, SendEmailCommand },\n };\n\nexport interface Email {\n to?: string | null;\n from?: string;\n reference?: string;\n template?: string;\n idempotencyKey?: string;\n replyTo?: string | null;\n text?: string;\n attachments?: any[];\n subject: string;\n}\n\nexport const emailTransporter = (sourceArn: string | undefined, logger: any = console) => {\n const transporter = nodemailer.createTransport(\n {\n ...emailOptions,\n debug: true,\n } as any,\n {\n ses: {\n FromEmailAddressIdentityArn: sourceArn,\n },\n } as any\n );\n\n return {\n async sendMail(\n {\n to,\n reference,\n from,\n replyTo,\n template,\n text,\n attachments = [],\n idempotencyKey,\n subject,\n }: Email,\n idempotentCheck?: boolean\n ) {\n if (!to) {\n return;\n }\n const sender = from;\n const emailLogService = new EmailLogService();\n logger.info(\n `Sending email to ${to} with subject: ${subject}, reference: ${reference}, idempotencyKey: ${idempotencyKey}`\n );\n\n try {\n if (idempotentCheck) {\n const existingEmail =\n await emailLogService.getEmailByIdempotencyKey(idempotencyKey);\n if (existingEmail) {\n logger.info(`Email with idempotency key ${idempotencyKey} already sent`);\n return;\n }\n }\n const result = await transporter.sendMail({\n to,\n from: sender,\n replyTo: replyTo ?? undefined,\n html: template,\n text,\n subject,\n attachments,\n });\n\n // Log successful email\n await emailLogService.logEmail({\n subject: subject || 'No Subject',\n reference,\n sender: sender || 'Unknown Sender',\n idempotencyKey,\n receiver: to,\n status: 'success',\n });\n\n return result;\n } catch (error) {\n // Log failed email\n await emailLogService.logEmail({\n subject: subject || 'No Subject',\n sender: sender || 'Unknown Sender',\n receiver: to,\n status: 'failed',\n error: error instanceof Error ? error.message : String(error),\n });\n\n throw error;\n }\n },\n };\n};\n","import { DynamoDBClient } from '@aws-sdk/client-dynamodb';\nimport { DynamoDBDocumentClient, PutCommand, QueryCommand } from '@aws-sdk/lib-dynamodb';\nimport { v4 as uuidv4 } from 'uuid';\n\ninterface EmailLog {\n reference?: string;\n timestamp: number;\n subject: string;\n sender: string;\n receiver: string;\n idempotencyKey?: string;\n status: 'success' | 'failed';\n error?: string;\n}\n\nexport class EmailLogService {\n private readonly tableName: string;\n private readonly dynamoClient: DynamoDBClient | undefined;\n private readonly docClient: DynamoDBDocumentClient | undefined;\n\n constructor() {\n this.tableName = process.env.EMAIL_TABLE_NAME!;\n if (this.tableName) {\n this.dynamoClient = new DynamoDBClient({\n region: process.env.AWS_REGION,\n });\n this.docClient = DynamoDBDocumentClient.from(this.dynamoClient);\n }\n }\n\n async logEmail({\n subject,\n sender,\n receiver,\n status,\n error,\n idempotencyKey,\n reference = uuidv4(),\n }: Omit<EmailLog, 'timestamp'>): Promise<string> {\n if (!this.docClient) {\n return reference;\n }\n const timestamp = Date.now();\n\n const log: EmailLog = {\n reference,\n timestamp,\n subject,\n sender,\n receiver,\n status,\n idempotencyKey,\n ...(error && { error }),\n };\n\n await this.docClient.send(\n new PutCommand({\n TableName: this.tableName,\n Item: log,\n })\n );\n return reference;\n }\n\n async getEmailByIdempotencyKey(idempotencyKey: string | undefined) {\n if (!idempotencyKey || !this.docClient) {\n return null;\n }\n\n const result = await this.docClient.send(\n new QueryCommand({\n TableName: this.tableName,\n IndexName: 'idempotency-key-index',\n KeyConditionExpression: 'idempotencyKey = :idempotencyKey',\n ExpressionAttributeValues: {\n ':idempotencyKey': idempotencyKey,\n },\n Limit: 1,\n })\n );\n return result.Items?.[0] as EmailLog | null;\n }\n}\n","import fs from 'node:fs';\nimport { formatters, dates } from '@xcelsior/utils';\nimport handlebars from 'handlebars';\nimport _ from 'lodash';\nimport moment from 'moment';\n\nconst { getDateTime } = dates;\nconst { convertMoneyFormat, moneyFormat } = formatters;\n\nhandlebars.registerHelper('multiply', (a, b) => a * b);\n\nhandlebars.registerHelper('datetime', (a) => moment(getDateTime(a)).format('DD/MM/YYYY HH:mm'));\n\nhandlebars.registerHelper('moneyFormat', (a, ...others) => {\n let currency = 'AUD' as const;\n if (others.length > 1) {\n currency = others[0] as any;\n }\n return moneyFormat(a, currency);\n});\n\nhandlebars.registerHelper('convertMoneyFormat', (amount, currency, rate = 1) =>\n convertMoneyFormat(amount, currency, rate)\n);\n\nhandlebars.registerHelper('titleCase', (string) => {\n if (!string) return '';\n return _.startCase(_.camelCase(string)).replace(/\\s/g, ' ');\n});\n\nhandlebars.registerHelper('choose', (a, b) => (a ? a : b));\n\nhandlebars.registerHelper(\n 'phoneFormat',\n (phone) =>\n `${phone.substring(0, phone.length / 2 - 2)}****${phone.substring(phone.length / 2 + 2)}`\n);\n\nhandlebars.registerHelper('title', (title) => _.startCase(title));\n\nhandlebars.registerHelper('eq', (a, b) => a === b);\nhandlebars.registerHelper('lowercase', (title: string) => title.toLowerCase());\n\nhandlebars.registerHelper('JSONparse', (string, key) => JSON.parse(string)[key]);\n\nhandlebars.registerHelper('incr', (a) => a + 1);\n\nhandlebars.registerHelper('gt', (a, b) => a > b);\n\nexport function renderTemplate(content: string, data: Record<string, any>): string {\n return handlebars.compile(content)(data);\n}\n\nexport async function renderTemplateFile(file: string, data: Record<string, any>) {\n const content = await fs.promises.readFile(file, {\n encoding: 'utf-8',\n });\n return renderTemplate(content, data);\n}\n"],"mappings":"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","names":["uuidv4","nodemailer","handlebars","moment","_","fs"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/email.ts","../src/email-log.ts","../src/template.ts"],"sourcesContent":["export * from './email';\nexport * from './template';\n","import { SendEmailCommand, SESv2Client } from '@aws-sdk/client-sesv2';\nimport nodemailer from 'nodemailer';\nimport { EmailLogService } from './email-log';\n\n/**\n * Template to use\n * await emailTransporter().sendMail({\n from: 'AusVie noreply<noreply@ausvie.com.au>',\n to: 'admin@ausvie.com.au',\n subject: 'Test', // Subject line\n text: 'Test text', // plaintext version\n html: `<div>${'Test text'}</div>`, // html version\n // attachments: [\n // {\n // filename,\n // content: fileData,\n // },\n // ],\n });\n */\n// Load the AWS SDK for Node.js\n// https://stackoverflow.com/questions/23042835/sending-mail-via-aws-ses-with-attachment-in-node-js\n\nconst sesClient = new SESv2Client({\n region: process.env.AWS_REGION,\n});\n\nconst emailOptions =\n process.env.EMAIL_TRANSPORT === 'smtp'\n ? {\n port: process.env.SMTP_PORT ? parseInt(process.env.SMTP_PORT, 10) : 587,\n host: process.env.SMTP_HOST,\n auth:\n process.env.SMTP_USER && process.env.SMTP_PASSWORD\n ? {\n user: process.env.SMTP_USER,\n pass: process.env.SMTP_PASSWORD,\n }\n : undefined,\n }\n : {\n SES: { sesClient, SendEmailCommand },\n };\n\nexport interface Email {\n to?: string | null;\n from?: string;\n reference?: string;\n template?: string;\n idempotencyKey?: string;\n replyTo?: string | null;\n text?: string;\n attachments?: any[];\n subject: string;\n}\n\nexport const emailTransporter = (logger: any = console) => {\n const transporter = nodemailer.createTransport(\n {\n ...emailOptions,\n debug: true,\n } as any,\n {\n ...(process.env.EMAIL_SOURCE_ARN\n ? {\n ses: {\n FromEmailAddressIdentityArn: process.env.EMAIL_SOURCE_ARN!,\n },\n }\n : undefined),\n } as any\n );\n\n return {\n async sendMail(\n {\n to,\n reference,\n from,\n replyTo,\n template,\n text,\n attachments = [],\n idempotencyKey,\n subject,\n }: Email,\n idempotentCheck?: boolean\n ) {\n if (!to) {\n return;\n }\n const sender = from;\n const emailLogService = new EmailLogService();\n logger.info(\n `Sending email to ${to} with subject: ${subject}, reference: ${reference}, idempotencyKey: ${idempotencyKey}`\n );\n\n try {\n if (idempotentCheck) {\n const existingEmail =\n await emailLogService.getEmailByIdempotencyKey(idempotencyKey);\n if (existingEmail) {\n logger.info(`Email with idempotency key ${idempotencyKey} already sent`);\n return;\n }\n }\n const result = await transporter.sendMail({\n to,\n from: sender,\n replyTo: replyTo ?? undefined,\n html: template,\n text,\n subject,\n attachments,\n });\n\n // Log successful email\n await emailLogService.logEmail({\n subject: subject || 'No Subject',\n reference,\n sender: sender || 'Unknown Sender',\n idempotencyKey,\n receiver: to,\n status: 'success',\n });\n\n return result;\n } catch (error) {\n // Log failed email\n await emailLogService.logEmail({\n subject: subject || 'No Subject',\n sender: sender || 'Unknown Sender',\n receiver: to,\n status: 'failed',\n error: error instanceof Error ? error.message : String(error),\n });\n\n throw error;\n }\n },\n };\n};\n","import { DynamoDBClient } from '@aws-sdk/client-dynamodb';\nimport { DynamoDBDocumentClient, PutCommand, QueryCommand } from '@aws-sdk/lib-dynamodb';\nimport { v4 as uuidv4 } from 'uuid';\n\ninterface EmailLog {\n reference?: string;\n timestamp: number;\n subject: string;\n sender: string;\n receiver: string;\n idempotencyKey?: string;\n status: 'success' | 'failed';\n error?: string;\n}\n\nexport class EmailLogService {\n private readonly tableName: string;\n private readonly dynamoClient: DynamoDBClient | undefined;\n private readonly docClient: DynamoDBDocumentClient | undefined;\n\n constructor() {\n this.tableName = process.env.EMAIL_TABLE_NAME!;\n if (this.tableName) {\n this.dynamoClient = new DynamoDBClient({\n region: process.env.AWS_REGION,\n });\n this.docClient = DynamoDBDocumentClient.from(this.dynamoClient);\n }\n }\n\n async logEmail({\n subject,\n sender,\n receiver,\n status,\n error,\n idempotencyKey,\n reference = uuidv4(),\n }: Omit<EmailLog, 'timestamp'>): Promise<string> {\n if (!this.docClient) {\n return reference;\n }\n const timestamp = Date.now();\n\n const log: EmailLog = {\n reference,\n timestamp,\n subject,\n sender,\n receiver,\n status,\n idempotencyKey,\n ...(error && { error }),\n };\n\n await this.docClient.send(\n new PutCommand({\n TableName: this.tableName,\n Item: log,\n })\n );\n return reference;\n }\n\n async getEmailByIdempotencyKey(idempotencyKey: string | undefined) {\n if (!idempotencyKey || !this.docClient) {\n return null;\n }\n\n const result = await this.docClient.send(\n new QueryCommand({\n TableName: this.tableName,\n IndexName: 'idempotency-key-index',\n KeyConditionExpression: 'idempotencyKey = :idempotencyKey',\n ExpressionAttributeValues: {\n ':idempotencyKey': idempotencyKey,\n },\n Limit: 1,\n })\n );\n return result.Items?.[0] as EmailLog | null;\n }\n}\n","import fs from 'node:fs';\nimport { formatters, dates } from '@xcelsior/utils';\nimport handlebars from 'handlebars';\nimport _ from 'lodash';\nimport moment from 'moment';\n\nconst { getDateTime } = dates;\nconst { convertMoneyFormat, moneyFormat } = formatters;\n\nhandlebars.registerHelper('multiply', (a, b) => a * b);\n\nhandlebars.registerHelper('datetime', (a) => moment(getDateTime(a)).format('DD/MM/YYYY HH:mm'));\n\nhandlebars.registerHelper('moneyFormat', (a, ...others) => {\n let currency = 'AUD' as const;\n if (others.length > 1) {\n currency = others[0] as any;\n }\n return moneyFormat(a, currency);\n});\n\nhandlebars.registerHelper('convertMoneyFormat', (amount, currency, rate = 1) =>\n convertMoneyFormat(amount, currency, rate)\n);\n\nhandlebars.registerHelper('titleCase', (string) => {\n if (!string) return '';\n return _.startCase(_.camelCase(string)).replace(/\\s/g, ' ');\n});\n\nhandlebars.registerHelper('choose', (a, b) => (a ? a : b));\n\nhandlebars.registerHelper(\n 'phoneFormat',\n (phone) =>\n `${phone.substring(0, phone.length / 2 - 2)}****${phone.substring(phone.length / 2 + 2)}`\n);\n\nhandlebars.registerHelper('title', (title) => _.startCase(title));\n\nhandlebars.registerHelper('eq', (a, b) => a === b);\nhandlebars.registerHelper('lowercase', (title: string) => title.toLowerCase());\n\nhandlebars.registerHelper('JSONparse', (string, key) => JSON.parse(string)[key]);\n\nhandlebars.registerHelper('incr', (a) => a + 1);\n\nhandlebars.registerHelper('gt', (a, b) => a > b);\n\nexport function renderTemplate(content: string, data: Record<string, any>): string {\n return handlebars.compile(content)(data);\n}\n\nexport async function renderTemplateFile(file: string, data: Record<string, any>) {\n const content = await fs.promises.readFile(file, {\n encoding: 'utf-8',\n });\n return renderTemplate(content, data);\n}\n"],"mappings":"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","names":["uuidv4","nodemailer","handlebars","moment","_","fs"]}
package/dist/index.mjs CHANGED
@@ -70,22 +70,28 @@ var EmailLogService = class {
70
70
  var sesClient = new SESv2Client({
71
71
  region: process.env.AWS_REGION
72
72
  });
73
- var emailOptions = process.env.NODE_ENV === "development" ? {
74
- port: 1025,
75
- host: process.env.SMTP_HOST
73
+ var emailOptions = process.env.EMAIL_TRANSPORT === "smtp" ? {
74
+ port: process.env.SMTP_PORT ? parseInt(process.env.SMTP_PORT, 10) : 587,
75
+ host: process.env.SMTP_HOST,
76
+ auth: process.env.SMTP_USER && process.env.SMTP_PASSWORD ? {
77
+ user: process.env.SMTP_USER,
78
+ pass: process.env.SMTP_PASSWORD
79
+ } : void 0
76
80
  } : {
77
81
  SES: { sesClient, SendEmailCommand }
78
82
  };
79
- var emailTransporter = (sourceArn, logger = console) => {
83
+ var emailTransporter = (logger = console) => {
80
84
  const transporter = nodemailer.createTransport(
81
85
  {
82
86
  ...emailOptions,
83
87
  debug: true
84
88
  },
85
89
  {
86
- ses: {
87
- FromEmailAddressIdentityArn: sourceArn
88
- }
90
+ ...process.env.EMAIL_SOURCE_ARN ? {
91
+ ses: {
92
+ FromEmailAddressIdentityArn: process.env.EMAIL_SOURCE_ARN
93
+ }
94
+ } : void 0
89
95
  }
90
96
  );
91
97
  return {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/email.ts","../src/email-log.ts","../src/template.ts"],"sourcesContent":["import { SendEmailCommand, SESv2Client } from '@aws-sdk/client-sesv2';\nimport nodemailer from 'nodemailer';\nimport { EmailLogService } from './email-log';\n\n/**\n * Template to use\n * await emailTransporter().sendMail({\n from: 'AusVie noreply<noreply@ausvie.com.au>',\n to: 'admin@ausvie.com.au',\n subject: 'Test', // Subject line\n text: 'Test text', // plaintext version\n html: `<div>${'Test text'}</div>`, // html version\n // attachments: [\n // {\n // filename,\n // content: fileData,\n // },\n // ],\n });\n */\n// Load the AWS SDK for Node.js\n// https://stackoverflow.com/questions/23042835/sending-mail-via-aws-ses-with-attachment-in-node-js\n\nconst sesClient = new SESv2Client({\n region: process.env.AWS_REGION,\n});\n\nconst emailOptions =\n process.env.NODE_ENV === 'development'\n ? {\n port: 1025,\n host: process.env.SMTP_HOST,\n }\n : {\n SES: { sesClient, SendEmailCommand },\n };\n\nexport interface Email {\n to?: string | null;\n from?: string;\n reference?: string;\n template?: string;\n idempotencyKey?: string;\n replyTo?: string | null;\n text?: string;\n attachments?: any[];\n subject: string;\n}\n\nexport const emailTransporter = (sourceArn: string | undefined, logger: any = console) => {\n const transporter = nodemailer.createTransport(\n {\n ...emailOptions,\n debug: true,\n } as any,\n {\n ses: {\n FromEmailAddressIdentityArn: sourceArn,\n },\n } as any\n );\n\n return {\n async sendMail(\n {\n to,\n reference,\n from,\n replyTo,\n template,\n text,\n attachments = [],\n idempotencyKey,\n subject,\n }: Email,\n idempotentCheck?: boolean\n ) {\n if (!to) {\n return;\n }\n const sender = from;\n const emailLogService = new EmailLogService();\n logger.info(\n `Sending email to ${to} with subject: ${subject}, reference: ${reference}, idempotencyKey: ${idempotencyKey}`\n );\n\n try {\n if (idempotentCheck) {\n const existingEmail =\n await emailLogService.getEmailByIdempotencyKey(idempotencyKey);\n if (existingEmail) {\n logger.info(`Email with idempotency key ${idempotencyKey} already sent`);\n return;\n }\n }\n const result = await transporter.sendMail({\n to,\n from: sender,\n replyTo: replyTo ?? undefined,\n html: template,\n text,\n subject,\n attachments,\n });\n\n // Log successful email\n await emailLogService.logEmail({\n subject: subject || 'No Subject',\n reference,\n sender: sender || 'Unknown Sender',\n idempotencyKey,\n receiver: to,\n status: 'success',\n });\n\n return result;\n } catch (error) {\n // Log failed email\n await emailLogService.logEmail({\n subject: subject || 'No Subject',\n sender: sender || 'Unknown Sender',\n receiver: to,\n status: 'failed',\n error: error instanceof Error ? error.message : String(error),\n });\n\n throw error;\n }\n },\n };\n};\n","import { DynamoDBClient } from '@aws-sdk/client-dynamodb';\nimport { DynamoDBDocumentClient, PutCommand, QueryCommand } from '@aws-sdk/lib-dynamodb';\nimport { v4 as uuidv4 } from 'uuid';\n\ninterface EmailLog {\n reference?: string;\n timestamp: number;\n subject: string;\n sender: string;\n receiver: string;\n idempotencyKey?: string;\n status: 'success' | 'failed';\n error?: string;\n}\n\nexport class EmailLogService {\n private readonly tableName: string;\n private readonly dynamoClient: DynamoDBClient | undefined;\n private readonly docClient: DynamoDBDocumentClient | undefined;\n\n constructor() {\n this.tableName = process.env.EMAIL_TABLE_NAME!;\n if (this.tableName) {\n this.dynamoClient = new DynamoDBClient({\n region: process.env.AWS_REGION,\n });\n this.docClient = DynamoDBDocumentClient.from(this.dynamoClient);\n }\n }\n\n async logEmail({\n subject,\n sender,\n receiver,\n status,\n error,\n idempotencyKey,\n reference = uuidv4(),\n }: Omit<EmailLog, 'timestamp'>): Promise<string> {\n if (!this.docClient) {\n return reference;\n }\n const timestamp = Date.now();\n\n const log: EmailLog = {\n reference,\n timestamp,\n subject,\n sender,\n receiver,\n status,\n idempotencyKey,\n ...(error && { error }),\n };\n\n await this.docClient.send(\n new PutCommand({\n TableName: this.tableName,\n Item: log,\n })\n );\n return reference;\n }\n\n async getEmailByIdempotencyKey(idempotencyKey: string | undefined) {\n if (!idempotencyKey || !this.docClient) {\n return null;\n }\n\n const result = await this.docClient.send(\n new QueryCommand({\n TableName: this.tableName,\n IndexName: 'idempotency-key-index',\n KeyConditionExpression: 'idempotencyKey = :idempotencyKey',\n ExpressionAttributeValues: {\n ':idempotencyKey': idempotencyKey,\n },\n Limit: 1,\n })\n );\n return result.Items?.[0] as EmailLog | null;\n }\n}\n","import fs from 'node:fs';\nimport { formatters, dates } from '@xcelsior/utils';\nimport handlebars from 'handlebars';\nimport _ from 'lodash';\nimport moment from 'moment';\n\nconst { getDateTime } = dates;\nconst { convertMoneyFormat, moneyFormat } = formatters;\n\nhandlebars.registerHelper('multiply', (a, b) => a * b);\n\nhandlebars.registerHelper('datetime', (a) => moment(getDateTime(a)).format('DD/MM/YYYY HH:mm'));\n\nhandlebars.registerHelper('moneyFormat', (a, ...others) => {\n let currency = 'AUD' as const;\n if (others.length > 1) {\n currency = others[0] as any;\n }\n return moneyFormat(a, currency);\n});\n\nhandlebars.registerHelper('convertMoneyFormat', (amount, currency, rate = 1) =>\n convertMoneyFormat(amount, currency, rate)\n);\n\nhandlebars.registerHelper('titleCase', (string) => {\n if (!string) return '';\n return _.startCase(_.camelCase(string)).replace(/\\s/g, ' ');\n});\n\nhandlebars.registerHelper('choose', (a, b) => (a ? a : b));\n\nhandlebars.registerHelper(\n 'phoneFormat',\n (phone) =>\n `${phone.substring(0, phone.length / 2 - 2)}****${phone.substring(phone.length / 2 + 2)}`\n);\n\nhandlebars.registerHelper('title', (title) => _.startCase(title));\n\nhandlebars.registerHelper('eq', (a, b) => a === b);\nhandlebars.registerHelper('lowercase', (title: string) => title.toLowerCase());\n\nhandlebars.registerHelper('JSONparse', (string, key) => JSON.parse(string)[key]);\n\nhandlebars.registerHelper('incr', (a) => a + 1);\n\nhandlebars.registerHelper('gt', (a, b) => a > b);\n\nexport function renderTemplate(content: string, data: Record<string, any>): string {\n return handlebars.compile(content)(data);\n}\n\nexport async function renderTemplateFile(file: string, data: Record<string, any>) {\n const content = await fs.promises.readFile(file, {\n encoding: 'utf-8',\n });\n return renderTemplate(content, data);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/email.ts","../src/email-log.ts","../src/template.ts"],"sourcesContent":["import { SendEmailCommand, SESv2Client } from '@aws-sdk/client-sesv2';\nimport nodemailer from 'nodemailer';\nimport { EmailLogService } from './email-log';\n\n/**\n * Template to use\n * await emailTransporter().sendMail({\n from: 'AusVie noreply<noreply@ausvie.com.au>',\n to: 'admin@ausvie.com.au',\n subject: 'Test', // Subject line\n text: 'Test text', // plaintext version\n html: `<div>${'Test text'}</div>`, // html version\n // attachments: [\n // {\n // filename,\n // content: fileData,\n // },\n // ],\n });\n */\n// Load the AWS SDK for Node.js\n// https://stackoverflow.com/questions/23042835/sending-mail-via-aws-ses-with-attachment-in-node-js\n\nconst sesClient = new SESv2Client({\n region: process.env.AWS_REGION,\n});\n\nconst emailOptions =\n process.env.EMAIL_TRANSPORT === 'smtp'\n ? {\n port: process.env.SMTP_PORT ? parseInt(process.env.SMTP_PORT, 10) : 587,\n host: process.env.SMTP_HOST,\n auth:\n process.env.SMTP_USER && process.env.SMTP_PASSWORD\n ? {\n user: process.env.SMTP_USER,\n pass: process.env.SMTP_PASSWORD,\n }\n : undefined,\n }\n : {\n SES: { sesClient, SendEmailCommand },\n };\n\nexport interface Email {\n to?: string | null;\n from?: string;\n reference?: string;\n template?: string;\n idempotencyKey?: string;\n replyTo?: string | null;\n text?: string;\n attachments?: any[];\n subject: string;\n}\n\nexport const emailTransporter = (logger: any = console) => {\n const transporter = nodemailer.createTransport(\n {\n ...emailOptions,\n debug: true,\n } as any,\n {\n ...(process.env.EMAIL_SOURCE_ARN\n ? {\n ses: {\n FromEmailAddressIdentityArn: process.env.EMAIL_SOURCE_ARN!,\n },\n }\n : undefined),\n } as any\n );\n\n return {\n async sendMail(\n {\n to,\n reference,\n from,\n replyTo,\n template,\n text,\n attachments = [],\n idempotencyKey,\n subject,\n }: Email,\n idempotentCheck?: boolean\n ) {\n if (!to) {\n return;\n }\n const sender = from;\n const emailLogService = new EmailLogService();\n logger.info(\n `Sending email to ${to} with subject: ${subject}, reference: ${reference}, idempotencyKey: ${idempotencyKey}`\n );\n\n try {\n if (idempotentCheck) {\n const existingEmail =\n await emailLogService.getEmailByIdempotencyKey(idempotencyKey);\n if (existingEmail) {\n logger.info(`Email with idempotency key ${idempotencyKey} already sent`);\n return;\n }\n }\n const result = await transporter.sendMail({\n to,\n from: sender,\n replyTo: replyTo ?? undefined,\n html: template,\n text,\n subject,\n attachments,\n });\n\n // Log successful email\n await emailLogService.logEmail({\n subject: subject || 'No Subject',\n reference,\n sender: sender || 'Unknown Sender',\n idempotencyKey,\n receiver: to,\n status: 'success',\n });\n\n return result;\n } catch (error) {\n // Log failed email\n await emailLogService.logEmail({\n subject: subject || 'No Subject',\n sender: sender || 'Unknown Sender',\n receiver: to,\n status: 'failed',\n error: error instanceof Error ? error.message : String(error),\n });\n\n throw error;\n }\n },\n };\n};\n","import { DynamoDBClient } from '@aws-sdk/client-dynamodb';\nimport { DynamoDBDocumentClient, PutCommand, QueryCommand } from '@aws-sdk/lib-dynamodb';\nimport { v4 as uuidv4 } from 'uuid';\n\ninterface EmailLog {\n reference?: string;\n timestamp: number;\n subject: string;\n sender: string;\n receiver: string;\n idempotencyKey?: string;\n status: 'success' | 'failed';\n error?: string;\n}\n\nexport class EmailLogService {\n private readonly tableName: string;\n private readonly dynamoClient: DynamoDBClient | undefined;\n private readonly docClient: DynamoDBDocumentClient | undefined;\n\n constructor() {\n this.tableName = process.env.EMAIL_TABLE_NAME!;\n if (this.tableName) {\n this.dynamoClient = new DynamoDBClient({\n region: process.env.AWS_REGION,\n });\n this.docClient = DynamoDBDocumentClient.from(this.dynamoClient);\n }\n }\n\n async logEmail({\n subject,\n sender,\n receiver,\n status,\n error,\n idempotencyKey,\n reference = uuidv4(),\n }: Omit<EmailLog, 'timestamp'>): Promise<string> {\n if (!this.docClient) {\n return reference;\n }\n const timestamp = Date.now();\n\n const log: EmailLog = {\n reference,\n timestamp,\n subject,\n sender,\n receiver,\n status,\n idempotencyKey,\n ...(error && { error }),\n };\n\n await this.docClient.send(\n new PutCommand({\n TableName: this.tableName,\n Item: log,\n })\n );\n return reference;\n }\n\n async getEmailByIdempotencyKey(idempotencyKey: string | undefined) {\n if (!idempotencyKey || !this.docClient) {\n return null;\n }\n\n const result = await this.docClient.send(\n new QueryCommand({\n TableName: this.tableName,\n IndexName: 'idempotency-key-index',\n KeyConditionExpression: 'idempotencyKey = :idempotencyKey',\n ExpressionAttributeValues: {\n ':idempotencyKey': idempotencyKey,\n },\n Limit: 1,\n })\n );\n return result.Items?.[0] as EmailLog | null;\n }\n}\n","import fs from 'node:fs';\nimport { formatters, dates } from '@xcelsior/utils';\nimport handlebars from 'handlebars';\nimport _ from 'lodash';\nimport moment from 'moment';\n\nconst { getDateTime } = dates;\nconst { convertMoneyFormat, moneyFormat } = formatters;\n\nhandlebars.registerHelper('multiply', (a, b) => a * b);\n\nhandlebars.registerHelper('datetime', (a) => moment(getDateTime(a)).format('DD/MM/YYYY HH:mm'));\n\nhandlebars.registerHelper('moneyFormat', (a, ...others) => {\n let currency = 'AUD' as const;\n if (others.length > 1) {\n currency = others[0] as any;\n }\n return moneyFormat(a, currency);\n});\n\nhandlebars.registerHelper('convertMoneyFormat', (amount, currency, rate = 1) =>\n convertMoneyFormat(amount, currency, rate)\n);\n\nhandlebars.registerHelper('titleCase', (string) => {\n if (!string) return '';\n return _.startCase(_.camelCase(string)).replace(/\\s/g, ' ');\n});\n\nhandlebars.registerHelper('choose', (a, b) => (a ? a : b));\n\nhandlebars.registerHelper(\n 'phoneFormat',\n (phone) =>\n `${phone.substring(0, phone.length / 2 - 2)}****${phone.substring(phone.length / 2 + 2)}`\n);\n\nhandlebars.registerHelper('title', (title) => _.startCase(title));\n\nhandlebars.registerHelper('eq', (a, b) => a === b);\nhandlebars.registerHelper('lowercase', (title: string) => title.toLowerCase());\n\nhandlebars.registerHelper('JSONparse', (string, key) => JSON.parse(string)[key]);\n\nhandlebars.registerHelper('incr', (a) => a + 1);\n\nhandlebars.registerHelper('gt', (a, b) => a > b);\n\nexport function renderTemplate(content: string, data: Record<string, any>): string {\n return handlebars.compile(content)(data);\n}\n\nexport async function renderTemplateFile(file: string, data: Record<string, any>) {\n const content = await fs.promises.readFile(file, {\n encoding: 'utf-8',\n });\n return renderTemplate(content, data);\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcelsior/email",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "exports": {
5
5
  ".": {
6
6
  "import": "./src/index.ts",
package/src/email.ts CHANGED
@@ -26,10 +26,17 @@ const sesClient = new SESv2Client({
26
26
  });
27
27
 
28
28
  const emailOptions =
29
- process.env.NODE_ENV === 'development'
29
+ process.env.EMAIL_TRANSPORT === 'smtp'
30
30
  ? {
31
- port: 1025,
31
+ port: process.env.SMTP_PORT ? parseInt(process.env.SMTP_PORT, 10) : 587,
32
32
  host: process.env.SMTP_HOST,
33
+ auth:
34
+ process.env.SMTP_USER && process.env.SMTP_PASSWORD
35
+ ? {
36
+ user: process.env.SMTP_USER,
37
+ pass: process.env.SMTP_PASSWORD,
38
+ }
39
+ : undefined,
33
40
  }
34
41
  : {
35
42
  SES: { sesClient, SendEmailCommand },
@@ -47,16 +54,20 @@ export interface Email {
47
54
  subject: string;
48
55
  }
49
56
 
50
- export const emailTransporter = (sourceArn: string | undefined, logger: any = console) => {
57
+ export const emailTransporter = (logger: any = console) => {
51
58
  const transporter = nodemailer.createTransport(
52
59
  {
53
60
  ...emailOptions,
54
61
  debug: true,
55
62
  } as any,
56
63
  {
57
- ses: {
58
- FromEmailAddressIdentityArn: sourceArn,
59
- },
64
+ ...(process.env.EMAIL_SOURCE_ARN
65
+ ? {
66
+ ses: {
67
+ FromEmailAddressIdentityArn: process.env.EMAIL_SOURCE_ARN!,
68
+ },
69
+ }
70
+ : undefined),
60
71
  } as any
61
72
  );
62
73