@b2y/email-service 1.0.1 → 1.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.
package/Config.js ADDED
@@ -0,0 +1,41 @@
1
+ // config.js
2
+ require('dotenv').config();
3
+ const Config = {
4
+ // Email Provider Configuration
5
+ emailProvider: process.env.EMAIL_PROVIDER,
6
+
7
+ // Nodemailer Configuration
8
+ emailService: process.env.EMAIL_SERVICE,
9
+ emailHost: process.env.EMAIL_HOST,
10
+ smtpPort: process.env.SMTP_PORT,
11
+ emailUser: process.env.EMAIL_USERNAME,
12
+ emailPass: process.env.EMAIL_PASSWORD,
13
+ fromEmail: process.env.FROM_EMAIL,
14
+
15
+ // SendGrid Configuration
16
+ sendGridApiKey: process.env.SENDGRID_API_KEY,
17
+
18
+ // Postmark Configuration
19
+ postmarkApiKey: process.env.POSTMARK_API_KEY,
20
+ postmarkFromEmail: process.env.POSTMARK_FROM_EMAIL,
21
+
22
+ // Amazon SES Configuration
23
+ awsRegion: process.env.AWS_REGION,
24
+ awsAccessKeyId: process.env.AWS_ACCESS_KEY_ID,
25
+ awsSecretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
26
+ awsFromEmail: process.env.AWS_FROM_EMAIL,
27
+
28
+ defaultLanguage: process.env.DEFAULT_LANGUAGE,
29
+
30
+ // Database Configuration (if using database for templates)
31
+ dbHost: process.env.DB_HOST,
32
+ dbPort: process.env.DB_PORT,
33
+ dbName: process.env.DB_DATABASE,
34
+ dbUser: process.env.DB_USER,
35
+ dbPass: process.env.DB_PASSWORD,
36
+ dbschema:process.env.DB_SCHEMA,
37
+ dbssl:process.env.DBSSL,
38
+
39
+ };
40
+
41
+ module.exports = Config;
package/Logger.js ADDED
@@ -0,0 +1,6 @@
1
+ require('dotenv').config();
2
+ const { createLogger } = require('b2y-logger');
3
+
4
+ const logger = createLogger();
5
+
6
+ module.exports = logger;
package/README.md CHANGED
@@ -1,18 +1,18 @@
1
- # email-service
2
-
3
- A reusable, flexible Node.js email service supporting multiple providers like **Nodemailer (Gmail)** and **SendGrid**, with support for **dynamic HTML templates** using Handlebars.
4
-
5
- # Features
6
-
7
- - Common interface for multiple email providers
8
- - Supports Gmail via Nodemailer and SendGrid (easy to extend more)
9
- - Handlebars-based HTML templates with variables
10
- - Easily switch between providers without changing your code
11
- - Dynamic template loading from the filesystem
12
- - Configurable via environment variables or direct parameters
13
-
14
- ---
15
-
16
- # Installation
17
-
18
- npm install @ourgitname/email-service
1
+ # email-service
2
+
3
+ A reusable, flexible Node.js email service supporting multiple providers like **Nodemailer (Gmail)** and **SendGrid**, with support for **dynamic HTML templates** using Handlebars.
4
+
5
+ # Features
6
+
7
+ - Common interface for multiple email providers
8
+ - Supports Gmail via Nodemailer and SendGrid (easy to extend more)
9
+ - Handlebars-based HTML templates with variables
10
+ - Easily switch between providers without changing your code
11
+ - Dynamic template loading from the filesystem
12
+ - Configurable via environment variables or direct parameters
13
+
14
+ ---
15
+
16
+ # Installation
17
+
18
+ npm install @ourgitname/email-service
package/index.js CHANGED
@@ -1,23 +1,78 @@
1
- const BaseProvider = require('./providers/BaseProvider');
2
- const NodemailerProvider = require('./providers/NodemailerProvider');
3
- const SendGridProvider = require('./providers/SendGridProvider');
4
- const TemplateEngine = require('./utils/TemplateEngine');
5
-
6
- // Create a singleton instance of TemplateEngine for convenience
7
- const templateEngine = new TemplateEngine();
8
-
9
- const EmailService = require('./EmailService');
10
-
11
- module.exports = {
12
- // Providers
13
- BaseProvider,
14
- NodemailerProvider,
15
- SendGridProvider,
16
-
17
- // Template utilities
18
- TemplateEngine, // Export the class itself
19
- templateEngine, // Export a singleton instance
20
-
21
- // Service
22
- EmailService
1
+ const BaseProvider = require('./providers/BaseProvider');
2
+ const NodeMailerProvider = require('./providers/NodeMailerProvider');
3
+ const SendgridProvider = require('./providers/SendgridProvider');
4
+ const PostmarkProvider = require('./providers/PostmarkProvider');
5
+ const AmazonSesProvider = require('./providers/AmazonSESProvider');
6
+ const TemplateEngine = require('./utils/TemplateEngine');
7
+ const EmailService = require('./service/EmailService');
8
+ const Config = require('./Config');
9
+ const logger = require('./Logger');
10
+ const { sequelize, EmailTemplate } = require('./model/Connect');
11
+ const { EmailProvider } = require('./enum/EmailProvider');
12
+ const StatusMessage = require('./constants/StatusMessageConstants');
13
+ // Initialize database connection
14
+ async function initializeDatabase() {
15
+ try {
16
+ await sequelize.authenticate();
17
+ logger.info(StatusMessage.DB_CONNECTION_SUCCESS);
18
+ return { EmailTemplate };
19
+ } catch (error) {
20
+ logger.error(StatusMessage.DB_CONNECTION_FAILED, error);
21
+ throw error;
22
+ }
23
+ }
24
+
25
+ // Factory function to create email provider
26
+ function createEmailProvider() {
27
+ const provider = (Config.emailProvider || '').toLowerCase().trim();
28
+
29
+ switch (provider) {
30
+ case EmailProvider.NODE_MAILER:
31
+ return new NodeMailerProvider();
32
+ case EmailProvider.SENDGRID:
33
+ return new SendgridProvider();
34
+ case EmailProvider.POSTMARK:
35
+ return new PostmarkProvider();
36
+ case EmailProvider.AMAZON_SES:
37
+ return new AmazonSesProvider();
38
+ default:
39
+ logger.warn(StatusMessage.PROVIDER_DEFAULT_FALLBACK);
40
+ return new NodeMailerProvider();
41
+ }
42
+ }
43
+
44
+ // Factory function to create email service
45
+ async function createEmailService(options = {}) {
46
+ try {
47
+ const provider = options.provider || createEmailProvider();
48
+ const dbClient = options.dbClient || await initializeDatabase();
49
+
50
+ return new EmailService(provider, dbClient);
51
+ } catch (error) {
52
+ logger.error(`Failed to create email service: ${error.message}`);
53
+ throw error;
54
+ }
55
+ }
56
+
57
+ // Create a singleton instance
58
+ let defaultEmailService;
59
+ async function getEmailService() {
60
+ if (!defaultEmailService) {
61
+ defaultEmailService = await createEmailService();
62
+ }
63
+ return defaultEmailService;
64
+ }
65
+
66
+ module.exports = {
67
+ BaseProvider,
68
+ NodeMailerProvider,
69
+ SendgridProvider,
70
+ PostmarkProvider,
71
+ AmazonSesProvider,
72
+ EmailService,
73
+ TemplateEngine,
74
+ createEmailProvider,
75
+ createEmailService,
76
+ getEmailService,
77
+ Config
23
78
  };
package/package.json CHANGED
@@ -1,43 +1,49 @@
1
- {
2
- "name": "@b2y/email-service",
3
- "version": "1.0.1",
4
- "main": "index.js",
5
- "exports": {
6
- ".": "./index.js",
7
- "./providers": "./providers/index.js",
8
- "./providers/base": "./providers/BaseProvider.js",
9
- "./providers/nodemailer": "./providers/NodemailerProvider.js",
10
- "./providers/sendgrid": "./providers/SendGridProvider.js",
11
- "./utils/templateEngine": "./utils/TemplateEngine.js",
12
- "./EmailService": "./EmailService.js"
13
- },
14
- "files": [
15
- "providers/",
16
- "templates/",
17
- "utils/",
18
- "*.js"
19
- ],
20
- "scripts": {
21
- "test": "echo \"Error: no test specified\" && exit 1",
22
- "start": "node test.js"
23
- },
24
- "author": "",
25
- "license": "ISC",
26
- "description": "A flexible email service with support for multiple providers and custom templates",
27
- "keywords": [
28
- "email",
29
- "templates",
30
- "nodemailer",
31
- "sendgrid"
32
- ],
33
- "publishConfig": {
34
- "access": "public"
35
- },
36
- "dependencies": {
37
- "@sendgrid/mail": "^8.1.5",
38
- "dotenv": "^16.5.0",
39
- "handlebars": "^4.7.8",
40
- "log4js": "^6.9.1",
41
- "nodemailer": "^6.10.0"
42
- }
43
- }
1
+ {
2
+ "name": "@b2y/email-service",
3
+ "version": "1.0.3",
4
+ "main": "index.js",
5
+ "exports": {
6
+ ".": "./index.js",
7
+ "./providers": "./providers/index.js",
8
+ "./providers/base": "./providers/BaseProvider.js",
9
+ "./providers/nodemailer": "./providers/NodeMailerProvider.js",
10
+ "./providers/sendgrid": "./providers/SendGridProvider.js",
11
+ "./utils/templateEngine": "./utils/TemplateEngine.js",
12
+ "./EmailService": "./service/EmailService.js"
13
+ },
14
+ "files": [
15
+ "providers/",
16
+ "templates/",
17
+ "utils/",
18
+ "*.js"
19
+ ],
20
+ "scripts": {
21
+ "test": "echo \"Error: no test specified\" && exit 1",
22
+ "start": "node test.js"
23
+ },
24
+ "author": "",
25
+ "license": "ISC",
26
+ "description": "A flexible email service with support for multiple providers and custom templates",
27
+ "keywords": [
28
+ "email",
29
+ "templates",
30
+ "nodemailer",
31
+ "sendgrid"
32
+ ],
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "dependencies": {
37
+ "@aws-sdk/client-ses": "^3.957.0",
38
+ "@sendgrid/mail": "^8.1.5",
39
+ "b2y-logger": "^1.1.0",
40
+ "dotenv": "^16.5.0",
41
+ "handlebars": "^4.7.8",
42
+ "log4js": "^6.9.1",
43
+ "nodemailer": "^6.10.0",
44
+ "pg": "^8.16.3",
45
+ "pg-hstore": "^2.3.4",
46
+ "postmark": "^4.0.5",
47
+ "sequelize": "^6.37.7"
48
+ }
49
+ }
@@ -0,0 +1,77 @@
1
+ // providers/AmazonSES.js
2
+ const { SESClient, SendEmailCommand } = require('@aws-sdk/client-ses');
3
+ const BaseProvider = require('./BaseProvider');
4
+ const Config = require('../Config');
5
+ const logger = require('../Logger');
6
+ const StatusMessage = require('../constants/StatusMessageConstants');
7
+ const { EmailProvider } = require('../enum/EmailProvider');
8
+
9
+ class AmazonSESProvider extends BaseProvider {
10
+ constructor() {
11
+ super();
12
+ this.client = new SESClient({
13
+ region: Config.awsRegion,
14
+ credentials: {
15
+ accessKeyId: Config.awsAccessKeyId,
16
+ secretAccessKey: Config.awsSecretAccessKey
17
+ }
18
+ });
19
+ }
20
+
21
+ async sendEmail(options) {
22
+ try {
23
+ this.validateEmailOptions(options);
24
+
25
+ const toAddresses = this.formatRecipients(options.to);
26
+ const ccAddresses = options.cc ? this.formatRecipients(options.cc) : [];
27
+ const bccAddresses = options.bcc ? this.formatRecipients(options.bcc) : [];
28
+
29
+ const params = {
30
+ Source: options.from || Config.awsFromEmail,
31
+ Destination: {
32
+ ToAddresses: toAddresses,
33
+ ...(ccAddresses.length > 0 && { CcAddresses: ccAddresses }),
34
+ ...(bccAddresses.length > 0 && { BccAddresses: bccAddresses })
35
+ },
36
+ Message: {
37
+ Subject: {
38
+ Data: options.subject,
39
+ Charset: 'UTF-8'
40
+ },
41
+ Body: {
42
+ Html: {
43
+ Data: options.html,
44
+ Charset: 'UTF-8'
45
+ }
46
+ }
47
+ }
48
+ };
49
+
50
+ // Add attachments if present (SES requires using SendRawEmail for attachments)
51
+ if (options.attachments && options.attachments.length > 0) {
52
+ logger.warn('SES provider: Attachments require SendRawEmail. Consider implementing raw email support.');
53
+ }
54
+
55
+ const command = new SendEmailCommand(params);
56
+ const result = await this.client.send(command);
57
+
58
+ logger.info('Email sent successfully via Amazon SES', {
59
+ messageId: result.MessageId,
60
+ recipients: { to: options.to, cc: options.cc, bcc: options.bcc },
61
+ tenantId: options.tenantId
62
+ });
63
+
64
+ return {
65
+ success: true,
66
+ messageId: result.MessageId,
67
+ provider: EmailProvider.SES,
68
+ tenantId: options.tenantId
69
+ };
70
+ } catch (error) {
71
+ logger.error(`Amazon SES failed to send email: ${error.message}`);
72
+ throw new Error(`${StatusMessage.AMAZON_SES_SEND_ERROR}: ${error.message}`);
73
+ }
74
+ }
75
+ }
76
+
77
+ module.exports = AmazonSESProvider;
@@ -1,10 +1,74 @@
1
- const logger = require('../logger');
2
-
3
- class BaseProvider {
4
- async sendEmail(options) {
5
- logger.error('sendEmail method not implemented in BaseProvider');
6
- throw new Error('sendEmail method not implemented');
7
- }
8
- }
9
-
10
- module.exports = BaseProvider;
1
+ // providers/BaseProvider.js
2
+ const logger = require('../Logger');
3
+ const StatusMessage = require('../constants/StatusMessageConstants');
4
+ class BaseProvider {
5
+ constructor() {
6
+ if (new.target === BaseProvider) {
7
+ throw new Error(StatusMessage.CANNOT_INSTANTIATE_BASE);
8
+ }
9
+ }
10
+
11
+ async sendEmail(options) {
12
+ logger.error('sendEmail method not implemented in BaseProvider');
13
+ throw new Error(StatusMessage.PROVIDER_NOT_IMPLEMENTED);
14
+ }
15
+
16
+ validateEmailOptions(options) {
17
+ const { to, subject, html, from } = options;
18
+
19
+ if (!to || (Array.isArray(to) && to.length === 0)) {
20
+ throw new Error(StatusMessage.RECIPIENTS_REQUIRED);
21
+ }
22
+
23
+ if (!subject) {
24
+ throw new Error(StatusMessage.SUBJECT_REQUIRED);
25
+ }
26
+
27
+ if (!html) {
28
+ throw new Error(StatusMessage.HTML_CONTENT_REQUIRED);
29
+ }
30
+
31
+ return true;
32
+ }
33
+
34
+ formatRecipients(recipients) {
35
+ if (!recipients) return [];
36
+
37
+ if (typeof recipients === 'string') {
38
+ return recipients.split(',').map(r => r.trim());
39
+ }
40
+
41
+ if (Array.isArray(recipients)) {
42
+ return recipients;
43
+ }
44
+
45
+ return [];
46
+ }
47
+
48
+ buildMailOptions(options) {
49
+ const { to, cc, bcc, subject, html, from, attachments } = options;
50
+
51
+ const mailOptions = {
52
+ from,
53
+ to: this.formatRecipients(to),
54
+ subject,
55
+ html,
56
+ };
57
+
58
+ if (cc && cc.length > 0) {
59
+ mailOptions.cc = this.formatRecipients(cc);
60
+ }
61
+
62
+ if (bcc && bcc.length > 0) {
63
+ mailOptions.bcc = this.formatRecipients(bcc);
64
+ }
65
+
66
+ if (attachments && attachments.length > 0) {
67
+ mailOptions.attachments = attachments;
68
+ }
69
+
70
+ return mailOptions;
71
+ }
72
+ }
73
+
74
+ module.exports = BaseProvider;
@@ -0,0 +1,53 @@
1
+ // providers/NodeMailerProvider.js
2
+ const nodemailer = require('nodemailer');
3
+ const BaseProvider = require('./BaseProvider');
4
+ const Config = require('../Config');
5
+ const logger = require('../Logger');
6
+ const StatusMessage = require('../constants/StatusMessageConstants');
7
+ const { EmailProvider } = require('../enum/EmailProvider');
8
+
9
+ class NodeMailerProvider extends BaseProvider {
10
+ constructor() {
11
+ super();
12
+ this.transporter = nodemailer.createTransport({
13
+ service: Config.emailService,
14
+ host: Config.emailHost,
15
+ port: Config.smtpPort,
16
+ secure: Config.smtpPort,
17
+ auth: {
18
+ user: Config.emailUser,
19
+ pass: Config.emailPass,
20
+ },
21
+ });
22
+ }
23
+
24
+ async sendEmail(options) {
25
+ try {
26
+ this.validateEmailOptions(options);
27
+ const mailOptions = this.buildMailOptions({
28
+ ...options,
29
+ from: options.from || Config.emailUser
30
+ });
31
+
32
+ const result = await this.transporter.sendMail(mailOptions);
33
+
34
+ logger.info('Email sent successfully via Nodemailer', {
35
+ messageId: result.messageId,
36
+ recipients: { to: options.to, cc: options.cc, bcc: options.bcc },
37
+ tenantId: options.tenantId
38
+ });
39
+
40
+ return {
41
+ success: true,
42
+ messageId: result.messageId,
43
+ provider: EmailProvider.NODE_MAILER,
44
+ tenantId: options.tenantId
45
+ };
46
+ } catch (error) {
47
+ logger.error(`Nodemailer failed to send email: ${error.message}`);
48
+ throw new Error(`${StatusMessage.NODEMAILER_SEND_ERROR}: ${error.message}`);
49
+ }
50
+ }
51
+ }
52
+
53
+ module.exports = NodeMailerProvider;
@@ -0,0 +1,62 @@
1
+ // providers/PostmarkProvider.js
2
+ const postmark = require('postmark');
3
+ const BaseProvider = require('./BaseProvider');
4
+ const Config = require('../Config');
5
+ const logger = require('../Logger');
6
+ const StatusMessage = require('../constants/StatusMessageConstants');
7
+ const { EmailProvider } = require('../enum/EmailProvider');
8
+
9
+ class PostmarkProvider extends BaseProvider {
10
+ constructor() {
11
+ super();
12
+ this.client = new postmark.Client(Config.postmarkApiKey);
13
+ }
14
+
15
+ async sendEmail(options) {
16
+ try {
17
+ this.validateEmailOptions(options);
18
+ const mailOptions = {
19
+ From: options.from || Config.postmarkFromEmail,
20
+ To: this.formatRecipients(options.to).join(','),
21
+ Subject: options.subject,
22
+ HtmlBody: options.html,
23
+ };
24
+
25
+ if (options.cc && options.cc.length > 0) {
26
+ mailOptions.Cc = this.formatRecipients(options.cc).join(',');
27
+ }
28
+
29
+ if (options.bcc && options.bcc.length > 0) {
30
+ mailOptions.Bcc = this.formatRecipients(options.bcc).join(',');
31
+ }
32
+
33
+ if (options.attachments && options.attachments.length > 0) {
34
+ mailOptions.Attachments = options.attachments.map(att => ({
35
+ Name: att.filename,
36
+ Content: att.content,
37
+ ContentType: att.contentType
38
+ }));
39
+ }
40
+
41
+ const result = await this.client.sendEmail(mailOptions);
42
+
43
+ logger.info('Email sent successfully via Postmark', {
44
+ messageId: result.MessageID,
45
+ recipients: { to: options.to, cc: options.cc, bcc: options.bcc },
46
+ tenantId: options.tenantId
47
+ });
48
+
49
+ return {
50
+ success: true,
51
+ messageId: result.MessageID,
52
+ provider: EmailProvider.POSTMARK,
53
+ tenantId: options.tenantId
54
+ };
55
+ } catch (error) {
56
+ logger.error(`Postmark failed to send email: ${error.message}`);
57
+ throw new Error(`${StatusMessage.POSTMARK_SEND_ERROR}: ${error.message}`);
58
+ }
59
+ }
60
+ }
61
+
62
+ module.exports = PostmarkProvider;
@@ -1,33 +1,60 @@
1
- const sgMail = require('@sendgrid/mail');
2
- const BaseProvider = require('./BaseProvider');
3
- const config = require('../config');
4
- const logger = require('../logger');
5
-
6
- class SendGridProvider extends BaseProvider {
7
- constructor() {
8
- super();
9
- sgMail.setApiKey(config.sendGridApiKey);
10
- }
11
-
12
- async sendEmail({ to, subject, html }) {
13
- if (!to || !subject || !html) {
14
- logger.error('Missing required email fields: to, subject, or html');
15
- throw new Error('Missing required email fields: to, subject, or html');
16
- }
17
-
18
- try {
19
- return await sgMail.send({
20
- to,
21
- from: config.emailUser,
22
- subject,
23
- html,
24
- });
25
- } catch (error) {
26
- logger.error(`SendGrid failed to send email: ${error.message}`);
27
- throw new Error(`SendGrid failed to send email: ${error.message}`);
28
- }
29
- }
30
-
31
- }
32
-
33
- module.exports = SendGridProvider;
1
+ // providers/SendgridProvider.js
2
+ const sgMail = require('@sendgrid/mail');
3
+ const BaseProvider = require('./BaseProvider');
4
+ const Config = require('../Config');
5
+ const logger = require('../Logger');
6
+ const { EmailProvider } = require('../enum/EmailProvider');
7
+
8
+ class SendgridProvider extends BaseProvider {
9
+ constructor() {
10
+ super();
11
+ sgMail.setApiKey(Config.sendGridApiKey);
12
+ }
13
+
14
+ async sendEmail({ tenantId, to, cc, bcc, subject, html, from, attachments }) {
15
+ try {
16
+ this.validateEmailOptions({ to, subject, html, from });
17
+
18
+ const mailOptions = {
19
+ to: this.formatRecipients(to),
20
+ from: Config.fromEmail,
21
+ subject,
22
+ html,
23
+ };
24
+
25
+ if (cc && cc.length > 0) {
26
+ mailOptions.cc = this.formatRecipients(cc);
27
+ }
28
+
29
+ if (bcc && bcc.length > 0) {
30
+ mailOptions.bcc = this.formatRecipients(bcc);
31
+ }
32
+
33
+ if (attachments && attachments.length > 0) {
34
+ mailOptions.attachments = attachments;
35
+ }
36
+
37
+ const result = await sgMail.send(mailOptions);
38
+
39
+ logger.info('Email sent successfully via SendGrid', {
40
+ messageId: result[0].headers['x-message-id'],
41
+ recipients: { to, cc, bcc },
42
+ tenantId
43
+ });
44
+
45
+ return {
46
+ success: true,
47
+ messageId: result[0].headers['x-message-id'],
48
+ provider: EmailProvider.SENDGRID,
49
+ tenantId
50
+ };
51
+
52
+ } catch (error) {
53
+ logger.error(`SendGrid failed to send email : ${error.message}`);
54
+ throw new Error(`${StatusMessage.SENDGRID_SEND_ERROR}: ${error.message}`);
55
+ }
56
+ }
57
+ }
58
+
59
+ module.exports = SendgridProvider;
60
+
@@ -1,86 +1,99 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const handlebars = require('handlebars');
4
- const logger = require('../logger');
5
-
6
- class TemplateEngine {
7
- constructor(options = {}) {
8
- // Default templates directory in the module
9
- this.defaultTemplateDir = path.join(__dirname, '..', 'templates');
10
-
11
- // Custom templates directory provided by the user
12
- this.customTemplateDir = options.customTemplateDir || null;
13
-
14
- // Array to store additional template directories
15
- this.additionalTemplateDirs = [];
16
- }
17
-
18
- // Add a new template directory to search for templates
19
- addTemplateDirectory(dirPath, prepend = false) {
20
- if (!fs.existsSync(dirPath)) {
21
- logger.warn(`Template directory does not exist: ${dirPath}`);
22
- return false;
23
- }
24
-
25
- if (prepend) {
26
- this.additionalTemplateDirs.unshift(dirPath);
27
- } else {
28
- this.additionalTemplateDirs.push(dirPath);
29
- }
30
-
31
- logger.info(`Added template directory: ${dirPath}`);
32
- return true;
33
- }
34
-
35
- // Reset all additional template directories
36
- resetTemplateDirectories() {
37
- this.additionalTemplateDirs = [];
38
- logger.info('Reset additional template directories');
39
- }
40
-
41
- compileTemplate(templateName, variables) {
42
- let templateContent;
43
- let templatePath;
44
-
45
- // First try to find the template in the additional directories
46
- for (const dir of this.additionalTemplateDirs) {
47
- templatePath = path.join(dir, `${templateName}.html`);
48
- if (fs.existsSync(templatePath)) {
49
- templateContent = fs.readFileSync(templatePath, 'utf-8');
50
- logger.info(`Using template from additional directory: ${templatePath}`);
51
- break;
52
- }
53
- }
54
-
55
- // Then try the custom directory if no template found yet
56
- if (!templateContent && this.customTemplateDir) {
57
- templatePath = path.join(this.customTemplateDir, `${templateName}.html`);
58
- if (fs.existsSync(templatePath)) {
59
- templateContent = fs.readFileSync(templatePath, 'utf-8');
60
- logger.info(`Using custom template: ${templatePath}`);
61
- }
62
- }
63
-
64
- // Finally try the default directory if still no template found
65
- if (!templateContent) {
66
- templatePath = path.join(this.defaultTemplateDir, `${templateName}.html`);
67
- if (fs.existsSync(templatePath)) {
68
- templateContent = fs.readFileSync(templatePath, 'utf-8');
69
- logger.info(`Using default template: ${templatePath}`);
70
- } else {
71
- logger.error(`Template "${templateName}" not found in any configured template directory`);
72
- throw new Error(`Template "${templateName}" not found`);
73
- }
74
- }
75
-
76
- try {
77
- const template = handlebars.compile(templateContent);
78
- return template(variables);
79
- } catch (err) {
80
- logger.error(`Error compiling template "${templateName}": ${err.message}`);
81
- throw new Error(`Error compiling template "${templateName}": ${err.message}`);
82
- }
83
- }
84
- }
85
-
86
- module.exports = TemplateEngine;
1
+ const handlebars = require('handlebars');
2
+ const logger = require('../Logger');
3
+
4
+ class TemplateEngine {
5
+ constructor(dbClient) {
6
+ if (!dbClient) throw new Error(StatusMessage.DB_CLIENT_REQUIRED);
7
+ this.EmailTemplate = dbClient.EmailTemplate;
8
+ this.registerHelpers();
9
+ }
10
+
11
+ registerHelpers() {
12
+ handlebars.registerHelper('eq', (a, b) => a === b);
13
+ handlebars.registerHelper('ne', (a, b) => a !== b);
14
+ handlebars.registerHelper('gt', (a, b) => a > b);
15
+ handlebars.registerHelper('lt', (a, b) => a < b);
16
+ handlebars.registerHelper('uppercase', (str) => str ? str.toUpperCase() : '');
17
+ handlebars.registerHelper('lowercase', (str) => str ? str.toLowerCase() : '');
18
+ handlebars.registerHelper('formatDate', (date) => {
19
+ if (!date) return '';
20
+ return new Date(date).toLocaleDateString();
21
+ });
22
+ }
23
+
24
+ async getTemplateFromDB(templateName, tenantId, langCode) {
25
+ try {
26
+ logger.info(`Fetching template: ${templateName} in language: ${langCode}`);
27
+ const template = await this.EmailTemplate.findOne({
28
+ where: {
29
+ TemplateName: templateName,
30
+ TenantId: tenantId,
31
+ LangCode: langCode
32
+ }
33
+ });
34
+
35
+ if (!template) {
36
+ logger.warn(`Template "${templateName}" not found in language ${langCode}`);
37
+ const defaultTemplate = await this.EmailTemplate.findOne({
38
+ where: {
39
+ TemplateName: templateName,
40
+ TenantId: tenantId,
41
+ LangCode: langCode
42
+ }
43
+ });
44
+
45
+ if (!defaultTemplate) {
46
+ throw new Error(`Template "${templateName}" not found in any language`);
47
+ }
48
+
49
+ return {
50
+ subject: defaultTemplate.Subject,
51
+ body: defaultTemplate.Body
52
+ };
53
+ }
54
+
55
+ return {
56
+ subject: template.Subject,
57
+ body: template.Body
58
+ };
59
+ } catch (error) {
60
+ logger.error(`Error fetching template from DB: ${error.message}`);
61
+ throw error;
62
+ }
63
+ }
64
+
65
+ async compileTemplate(templateName, variables = {}, tenantId, langCode) {
66
+ try {
67
+ logger.info(`Compiling template: ${templateName} in language: ${langCode}`);
68
+
69
+ const templateData = await this.getTemplateFromDB(templateName, tenantId, langCode);
70
+
71
+ const subjectTemplate = handlebars.compile(templateData.subject);
72
+ const bodyTemplate = handlebars.compile(templateData.body);
73
+
74
+ return {
75
+ subject: subjectTemplate(variables),
76
+ body: bodyTemplate(variables)
77
+ };
78
+ } catch (error) {
79
+ logger.error(`Error compiling template "${templateName}": ${error.message}`);
80
+ throw error;
81
+ }
82
+ }
83
+
84
+ replacePlaceholders(content, placeholders = {}) {
85
+ if (!content || typeof content !== 'string') {
86
+ return content;
87
+ }
88
+
89
+ try {
90
+ const template = handlebars.compile(content);
91
+ return template(placeholders);
92
+ } catch (err) {
93
+ logger.error(`Error replacing placeholders: ${err.message}`);
94
+ return content;
95
+ }
96
+ }
97
+ }
98
+
99
+ module.exports = TemplateEngine;
package/EmailService.js DELETED
@@ -1,20 +0,0 @@
1
- const TemplateEngine = require('./utils/TemplateEngine');
2
-
3
- class EmailService {
4
- constructor(options = {}) {
5
- if (!options.provider) throw new Error('Email provider is required');
6
- this.provider = options.provider;
7
-
8
- // Create the template engine with custom directory if provided
9
- this.templateEngine = new TemplateEngine({
10
- customTemplateDir: options.customTemplateDir
11
- });
12
- }
13
-
14
- async sendTemplateEmail(templateName, to, subject, variables = {}) {
15
- const html = this.templateEngine.compileTemplate(templateName, variables);
16
- return this.provider.sendEmail({ to, subject, html });
17
- }
18
- }
19
-
20
- module.exports = EmailService;
package/config.js DELETED
@@ -1,9 +0,0 @@
1
- // config.js
2
- require('dotenv').config();
3
-
4
- module.exports = {
5
- emailService: process.env.EMAIL_SERVICE,
6
- emailUser: process.env.EMAIL_USER,
7
- emailPass: process.env.EMAIL_PASS,
8
- sendGridApiKey: process.env.SENDGRID_API_KEY,
9
- };
package/logger.js DELETED
@@ -1,81 +0,0 @@
1
- const log4js = require('log4js');
2
- const path = require('path');
3
- const fs = require('fs');
4
-
5
-
6
- // Get log directory from environment variable or use default
7
- const LOG_DIR = process.env.LOG_DIR || path.join(process.cwd(), '..', 'logs');
8
-
9
- // Ensure the log directory exists
10
- if (!fs.existsSync(LOG_DIR)) {
11
- fs.mkdirSync(LOG_DIR, { recursive: true });
12
- }
13
-
14
- log4js.configure({
15
- appenders: {
16
- console: { type: 'console' },
17
- combined: {
18
- type: 'file',
19
- filename: path.join(LOG_DIR, 'EmailTemplate.log'),
20
- maxLogSize: 5 * 1024 * 1024, // 5MB
21
- backups: 100,
22
- compress: true,
23
- keepFileExt: true
24
- }
25
- },
26
- categories: { default: { appenders: ['console', 'combined'], level: 'debug' } }
27
- });
28
-
29
- // Function to get the caller file name
30
- const getCallerFile = () => {
31
- const originalFunc = Error.prepareStackTrace;
32
- let callerFile;
33
-
34
- try {
35
- const err = new Error();
36
- Error.prepareStackTrace = (_, stack) => stack;
37
- const stack = err.stack;
38
-
39
- for (let i = 0; i < stack.length; i++) {
40
- const fileName = stack[i].getFileName();
41
- if (fileName && !fileName.includes('logger.js') && !fileName.includes('node_modules')) {
42
- callerFile = path.basename(fileName);
43
- break;
44
- }
45
- }
46
- } catch (error) {
47
- callerFile = 'unknown';
48
- }
49
-
50
- Error.prepareStackTrace = originalFunc;
51
- return callerFile || 'unknown';
52
- };
53
-
54
- // Custom logger function to attach file name dynamically
55
- const log = (level, message, data) => {
56
- const fileName = getCallerFile();
57
- log4js.getLogger(fileName)[level](message, data);
58
- };
59
-
60
- module.exports = {
61
- debug: (message, data) => log('debug', message, data),
62
- info: (message, data) => log('info', message, data),
63
- warn: (message, data) => log('warn', message, data),
64
- error: (message, errorOrData) => {
65
- let enhancedData = errorOrData;
66
-
67
- if (errorOrData instanceof Error) {
68
- const { file, line, column } = getErrorDetails(errorOrData);
69
- enhancedData = {
70
- error: errorOrData.message,
71
- stack: errorOrData.stack,
72
- location: `${file}:${line}:${column}`
73
- };
74
- } else if (errorOrData && errorOrData.error instanceof Error) {
75
- const { file, line, column } = getErrorDetails(errorOrData.error);
76
- enhancedData = { ...errorOrData, location: `${file}:${line}:${column}` };
77
- }
78
-
79
- log('error', message, enhancedData);
80
- }
81
- };
@@ -1,40 +0,0 @@
1
- const nodemailer = require('nodemailer');
2
- const BaseProvider = require('./BaseProvider');
3
- const config = require('../config');
4
- const logger = require('../logger');
5
-
6
- class NodemailerProvider extends BaseProvider {
7
- constructor() {
8
- super();
9
- this.transporter = nodemailer.createTransport({
10
- service: config.emailService,
11
- auth: {
12
- user: config.emailUser,
13
- pass: config.emailPass,
14
- },
15
- });
16
- }
17
-
18
-
19
- async sendEmail({ to, subject, html }) {
20
- if (!to || !subject || !html) {
21
- logger.error('Missing required email fields: to, subject, or html');
22
- throw new Error('Missing required email fields: to, subject, or html');
23
- }
24
-
25
- try {
26
- return await this.transporter.sendMail({
27
- from: config.emailUser,
28
- to,
29
- subject,
30
- html,
31
- });
32
- } catch (error) {
33
- logger.error(`Nodemailer failed to send email: ${error.message}`);
34
- throw new Error(`Nodemailer failed to send email: ${error.message}`);
35
- }
36
- }
37
-
38
- }
39
-
40
- module.exports = NodemailerProvider;
package/server.js DELETED
@@ -1,15 +0,0 @@
1
- const { compileTemplate } = require('./utils/TemplateEngine');
2
-
3
- class EmailService {
4
- constructor(provider) {
5
- if (!provider) throw new Error('Email provider is required');
6
- this.provider = provider;
7
- }
8
-
9
- async sendTemplateEmail(templateName, to, subject, variables = {}) {
10
- const html = compileTemplate(templateName, variables);
11
- return this.provider.sendEmail({ to, subject, html });
12
- }
13
- }
14
-
15
- module.exports = EmailService;
@@ -1,7 +0,0 @@
1
- <p>Dear User,</p>
2
- <p>Your OTP for password reset is: <strong>{{otp}}</strong></p>
3
- <p>This OTP is valid for 10 minutes.</p>
4
- <p>If you did not request this, please ignore this email.</p>
5
- <br>
6
- <p>Best Regards,</p>
7
- <p>TestCompany</p>
@@ -1,6 +0,0 @@
1
- <html>
2
- <body>
3
- <h1>Welcome, {{name}}!</h1>
4
- <p>Thanks for joining {{company}}.</p>
5
- </body>
6
- </html>