Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions apps/organization/templates/organization-invitation.template.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { escapeHtml } from '@credebl/common/common.utils';

export class OrganizationInviteTemplate {
public sendInviteEmailTemplate(
email: string,
Expand All @@ -17,7 +19,9 @@ export class OrganizationInviteTemplate {
: `After successful registration, you can log in to the platform and click on “Accept Organization Invitation” on your dashboard.`;

const Button = isUserExist ? `Accept Organization Invitation` : `Register on ${process.env.PLATFORM_NAME}`;

const safeEmail = escapeHtml(email);
const safeOrgName = escapeHtml(orgName);
const safeFirstName = escapeHtml(firstName);
return `<!DOCTYPE html>
<html lang="en">

Expand All @@ -36,13 +40,13 @@ export class OrganizationInviteTemplate {
<div style="font-family: Montserrat; font-style: normal; font-weight: 500;
font-size: 15px; line-height: 24px;color: #00000;">
<p style="margin-top:0px">
Hello ${email},
Hello ${safeEmail},
</p>
<p>
${firstName} has invited you to join “${orgName}” as a member.
${safeFirstName} has invited you to join “${safeOrgName}” as a member.

</p><ul>
<li><strong>Organization:</strong> ${orgName}</li>
<li><strong>Organization:</strong> ${safeOrgName}</li>
<li><strong>Role:</strong> Member</li>
</ul>
<p>
Expand Down
26 changes: 23 additions & 3 deletions apps/user/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { MicroserviceOptions, Transport } from '@nestjs/microservices';

import { CommonConstants } from '@credebl/common/common.constant';
import { HttpExceptionFilter } from 'libs/http-exception.filter';
import { Logger } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter';
import { UserModule } from './user.module';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { getNatsOptions } from '@credebl/common/nats.config';
import { CommonConstants } from '@credebl/common/common.constant';
import NestjsLoggerServiceAdapter from '@credebl/logger/nestjsLoggerServiceAdapter';

const logger = new Logger();

Expand All @@ -19,5 +20,24 @@ async function bootstrap(): Promise<void> {

await app.listen();
logger.log('User Microservice is listening to NATS ');
const supportedProviders = ['sendgrid', 'resend', 'smtp'] as const;
type EmailProvider = (typeof supportedProviders)[number];
const provider = process.env.EMAIL_PROVIDER?.toLowerCase();

if (!provider) {
Logger.warn(
`Email service is disabled because EMAIL_PROVIDER is not set. ` +
`Configure EMAIL_PROVIDER (sendgrid, resend, or smtp) to enable sending emails.`
);
} else if (!supportedProviders.includes(provider as EmailProvider)) {
Logger.warn(
`Unknown EMAIL_PROVIDER value "${process.env.EMAIL_PROVIDER}". ` +
`Supported providers are: sendgrid, resend, smtp. ` +
`Email service will be disabled.`
);
} else {
const Emailprovider = provider as EmailProvider;
logger.log(`Email provider configured: ${Emailprovider}`);
}
}
bootstrap();
4 changes: 3 additions & 1 deletion libs/common/src/common.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,9 @@ export enum CommonConstants {
PLATFORM_ADMIN_EMAIL = 'platform.admin@yopmail.com',
PLATFORM_ADMIN_ORG = 'Platform-admin',
PLATFORM_ADMIN_ORG_ROLE = 'platform_admin',
DEFAULT_EMAIL_PROVIDER = 'sendgrid',
SENDGRID_EMAIL_PROVIDER = 'sendgrid',
RESEND_EMAIL_PROVIDER = 'resend',
SMTP_EMAIL_PROVIDER = 'smtp',
USER_HOLDER_ROLE = 'holder',

//onBoarding Type
Expand Down
9 changes: 9 additions & 0 deletions libs/common/src/common.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,12 @@ export function shouldLoadOidcModules(): boolean {
const hide = 'true' === raw.toLowerCase();
return !hide;
}

export const escapeHtml = (value: string): string =>
value
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/\//g, '&#x2F;');
15 changes: 9 additions & 6 deletions libs/common/src/email.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,38 @@ import { Injectable, Logger } from '@nestjs/common';
import { CommonConstants } from './common.constant';
import { EmailDto } from './dtos/email.dto';
import { sendWithResend } from './resend-helper-file';
import { sendWithSMTP } from './smtp-helper-file';
import { sendWithSendGrid } from './send-grid-helper-file';

@Injectable()
export class EmailService {
private readonly logger = new Logger(EmailService.name);

async sendEmail(emailDto: EmailDto): Promise<boolean> {
const provider = process.env.EMAIL_PROVIDER?.toLowerCase() || CommonConstants.DEFAULT_EMAIL_PROVIDER;
const provider = process.env.EMAIL_PROVIDER?.toLowerCase();

this.logger.debug(`Email Provider is: ${provider}`);
let result: boolean;

try {
switch (provider) {
case 'sendgrid':
case CommonConstants.SENDGRID_EMAIL_PROVIDER:
result = await sendWithSendGrid(emailDto);
break;
case 'resend':
case CommonConstants.RESEND_EMAIL_PROVIDER:
result = await sendWithResend(emailDto);
break;
case CommonConstants.SMTP_EMAIL_PROVIDER:
result = await sendWithSMTP(emailDto);
break;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
default:
this.logger.warn(`Unknown email provider: ${provider}, defaulting to SendGrid.`);
result = await sendWithSendGrid(emailDto);
this.logger.warn(`Unknown email provider: ${provider}`);
return false;
}
} catch (error) {
this.logger.error(`Failed to send email using ${provider}: ${error.message}`);
throw error;
}

return result;
}
}
12 changes: 9 additions & 3 deletions libs/common/src/resend-helper-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@ import { Resend } from 'resend';

dotenv.config();

const emailProvider = process.env.EMAIL_PROVIDER;
const apiKey = process.env.RESEND_API_KEY;
if (!apiKey) {
throw new Error('Missing RESEND_API_KEY in environment variables.');

let resend: Resend | null = null;

if ('resend' === emailProvider) {
if (!apiKey) {
throw new Error('Missing RESEND_API_KEY in environment variables.');
}
resend = new Resend(apiKey);
}
const resend = new Resend(apiKey);

export const sendWithResend = async (emailDto: EmailDto): Promise<boolean> => {
try {
Expand Down
59 changes: 59 additions & 0 deletions libs/common/src/smtp-helper-file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as dotenv from 'dotenv';
import * as nodemailer from 'nodemailer';

import { EmailDto } from './dtos/email.dto';
import { Logger } from '@nestjs/common';

dotenv.config();

const emailProvider = process.env.EMAIL_PROVIDER?.toLowerCase();

let transporter: nodemailer.Transporter | null = null;

if ('smtp' === emailProvider) {
const { SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS } = process.env;

if (!SMTP_HOST || !SMTP_PORT || !SMTP_USER || !SMTP_PASS) {
throw new Error('Missing SMTP configuration. Required: SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS');
}

const port = Number(SMTP_PORT);

if (!Number.isInteger(port) || 0 >= port) {
throw new Error(`Invalid SMTP_PORT value: "${SMTP_PORT}". Must be a valid number.`);
}

transporter = nodemailer.createTransport({
host: SMTP_HOST,
port: Number(SMTP_PORT),
secure: 465 === Number(SMTP_PORT),
auth: {
user: SMTP_USER,
pass: SMTP_PASS
},
requireTLS: 587 === Number(SMTP_PORT)
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

export const sendWithSMTP = async (emailDto: EmailDto): Promise<boolean> => {
if (!transporter) {
Logger.error('SMTP email provider is not initialized');
return false;
}

try {
await transporter.sendMail({
from: emailDto.emailFrom,
to: emailDto.emailTo,
subject: emailDto.emailSubject,
text: emailDto.emailText,
html: emailDto.emailHtml,
Comment thread Fixed
attachments: emailDto.emailAttachments
});

return true;
} catch (error) {
Logger.error('Error while sending email with SMTP', error);
return false;
}
};