add email service

This commit is contained in:
Emily
2025-01-27 15:12:22 +01:00
parent 65c682c75d
commit 510bc2545a
23 changed files with 3128 additions and 54 deletions

3
email/src/index.ts Normal file
View File

@@ -0,0 +1,3 @@
import { start } from "./services/server";
start();

182
email/src/services/email.ts Normal file
View File

@@ -0,0 +1,182 @@
import { TransactionalEmailsApi, SendSmtpEmail } from '@getbrevo/brevo';
import * as TEMPLATE from './templates'
export class EmailService {
private static apiInstance = new TransactionalEmailsApi();
static init(apiKey: string) {
this.apiInstance.setApiKey(0, apiKey);
}
static async sendLimitEmail50(target: string, projectName: string) {
try {
const sendSmtpEmail = new SendSmtpEmail();
sendSmtpEmail.subject = "⚡ You've reached 50% limit on Litlyx";
sendSmtpEmail.sender = { "name": "Litlyx", "email": "help@litlyx.com" };
sendSmtpEmail.to = [{ "email": target }];
sendSmtpEmail.htmlContent = TEMPLATE.LIMIT_50_EMAIL
.replace(/\[Project Name\]/, projectName)
.toString();
await this.apiInstance.sendTransacEmail(sendSmtpEmail);
return true;
} catch (ex) {
console.error('ERROR SENDING EMAIL', ex);
return false;
}
}
static async sendLimitEmail90(target: string, projectName: string) {
try {
const sendSmtpEmail = new SendSmtpEmail();
sendSmtpEmail.subject = "⚡ You've reached 90% limit on Litlyx";
sendSmtpEmail.sender = { "name": "Litlyx", "email": "help@litlyx.com" };
sendSmtpEmail.to = [{ "email": target }];
sendSmtpEmail.htmlContent = TEMPLATE.LIMIT_90_EMAIL
.replace(/\[Project Name\]/, projectName)
.toString();
await this.apiInstance.sendTransacEmail(sendSmtpEmail);
return true;
} catch (ex) {
console.error('ERROR SENDING EMAIL', ex);
return false;
}
}
static async sendLimitEmailMax(target: string, projectName: string) {
try {
const sendSmtpEmail = new SendSmtpEmail();
sendSmtpEmail.subject = "🚨 You've reached your limit on Litlyx!";
sendSmtpEmail.sender = { "name": "Litlyx", "email": "help@litlyx.com" };
sendSmtpEmail.to = [{ "email": target }];
sendSmtpEmail.htmlContent = TEMPLATE.LIMIT_MAX_EMAIL
.replace(/\[Project Name\]/, projectName)
.toString();
await this.apiInstance.sendTransacEmail(sendSmtpEmail);
return true;
} catch (ex) {
console.error('ERROR SENDING EMAIL', ex);
return false;
}
}
static async sendWelcomeEmail(target: string) {
try {
const sendSmtpEmail = new SendSmtpEmail();
sendSmtpEmail.subject = "Welcome to Litlyx!";
sendSmtpEmail.sender = { "name": "Litlyx", "email": "help@litlyx.com" };
sendSmtpEmail.to = [{ "email": target }];
sendSmtpEmail.htmlContent = TEMPLATE.WELCOME_EMAIL;
await this.apiInstance.sendTransacEmail(sendSmtpEmail);
return true;
} catch (ex) {
console.error('ERROR SENDING EMAIL', ex);
return false;
}
}
static async sendPurchaseEmail(target: string, projectName: string) {
try {
const sendSmtpEmail = new SendSmtpEmail();
sendSmtpEmail.subject = "Thank You for Upgrading Your Litlyx Plan!";
sendSmtpEmail.sender = { "name": "Litlyx", "email": "help@litlyx.com" };
sendSmtpEmail.to = [{ "email": target }];
sendSmtpEmail.htmlContent = TEMPLATE.PURCHASE_EMAIL
.replace(/\[Project Name\]/, projectName)
.toString();
await this.apiInstance.sendTransacEmail(sendSmtpEmail);
return true;
} catch (ex) {
console.error('ERROR SENDING EMAIL', ex);
return false;
}
}
static async sendAnomalyVisitsEventsEmail(target: string, projectName: string,
data: {
visits: { _id: string, count: number }[],
events: { _id: string, count: number }[]
}) {
try {
const sendSmtpEmail = new SendSmtpEmail();
sendSmtpEmail.subject = "🔍 Unexpected Activity Detected by our AI";
sendSmtpEmail.sender = { "name": "Litlyx", "email": "help@litlyx.com" };
sendSmtpEmail.to = [{ "email": target }];
sendSmtpEmail.htmlContent = TEMPLATE.ANOMALY_VISITS_EVENTS_EMAIL
.replace(/\[Project Name\]/, projectName)
.replace(/\[ENTRIES\]/,
[
...data.visits.map(e => (`<li> Visits in date ${new Date(e._id).toLocaleDateString('en-EN')} [ ${e.count} ] </li>`)),
...data.events.map(e => (`<li> Events in date ${new Date(e._id).toLocaleDateString('en-EN')} [ ${e.count} ] </li>`))
]
.join('')
)
.toString();
await this.apiInstance.sendTransacEmail(sendSmtpEmail);
return true;
} catch (ex) {
console.error('ERROR SENDING EMAIL', ex);
return false;
}
}
static async sendAnomalyDomainEmail(target: string, projectName: string, domains: string[]) {
try {
const sendSmtpEmail = new SendSmtpEmail();
sendSmtpEmail.subject = "🔍 Suspicious dns detected by our AI";
sendSmtpEmail.sender = { "name": "Litlyx", "email": "help@litlyx.com" };
sendSmtpEmail.to = [{ "email": target }];
sendSmtpEmail.htmlContent = TEMPLATE.ANOMALY_DOMAIN_EMAIL
.replace(/\[Project Name\]/, projectName)
.replace(/\[CURRENT_DATE\]/, new Date().toLocaleDateString('en-EN'))
// .replace(/\[DNS_ENTRIES\]/,
// domains.map(e => (`<li> ${e} </li>`)).join('<br>')
.replace(/\[DNS_ENTRIES\]/, domains[0])
.toString();
await this.apiInstance.sendTransacEmail(sendSmtpEmail);
return true;
} catch (ex) {
console.error('ERROR SENDING EMAIL', ex);
return false;
}
}
static async sendConfirmEmail(target: string, link: string) {
try {
const sendSmtpEmail = new SendSmtpEmail();
sendSmtpEmail.subject = "Confirm your email";
sendSmtpEmail.sender = { "name": "Litlyx", "email": "no-reply@litlyx.com" };
sendSmtpEmail.to = [{ "email": target }];
sendSmtpEmail.htmlContent = TEMPLATE.CONFIRM_EMAIL
.replace(/\[CONFIRM_LINK\]/, link)
.toString();
await this.apiInstance.sendTransacEmail(sendSmtpEmail);
return true;
} catch (ex) {
console.error('ERROR SENDING EMAIL', ex);
return false;
}
}
static async sendResetPasswordEmail(target: string, newPassword: string) {
try {
const sendSmtpEmail = new SendSmtpEmail();
sendSmtpEmail.subject = "Password reset";
sendSmtpEmail.sender = { "name": "Litlyx", "email": "no-reply@litlyx.com" };
sendSmtpEmail.to = [{ "email": target }];
sendSmtpEmail.htmlContent = TEMPLATE.RESET_PASSWORD_EMAIL
.replace(/\[NEW_PASSWORD\]/, newPassword)
.toString();
await this.apiInstance.sendTransacEmail(sendSmtpEmail);
return true;
} catch (ex) {
console.error('ERROR SENDING EMAIL', ex);
return false;
}
}
}

View File

@@ -0,0 +1,134 @@
import express from 'express';
import cors from 'cors';
import { EmailService } from './email';
const TOKEN = process.env.TOKEN;
if (!TOKEN || TOKEN.length == 0) {
console.log('TOKEN not set');
process.exit();
}
const PORT = process.env.PORT;
if (!PORT || PORT.length == 0) {
console.log('PORT not set');
process.exit();
}
const BREVO_API_KEY = process.env.BREVO_API_KEY;
if (!BREVO_API_KEY || BREVO_API_KEY.length == 0) {
console.log('BREVO_API_KEY not set');
process.exit();
}
EmailService.init(BREVO_API_KEY);
const app = express();
app.use(cors());
app.use((req, res, next) => {
const token = req.header('x-litlyx-token');
if (token != TOKEN) {
res.status(403).json({ error: 'token not valid' });
return;
}
console.log(req.path, req.body);
next();
});
app.post('/send/confirm', express.json(), async (req, res) => {
try {
const { target, link } = req.body;
const ok = await EmailService.sendConfirmEmail(target, link);
res.json({ ok });
} catch (ex) {
res.status(500).json({ error: ex.message });
}
});
app.post('/send/welcome', express.json(), async (req, res) => {
try {
const { target } = req.body;
const ok = await EmailService.sendWelcomeEmail(target);
res.json({ ok });
} catch (ex) {
res.status(500).json({ error: ex.message });
}
});
app.post('/send/purchase', express.json(), async (req, res) => {
try {
const { target, projectName } = req.body;
const ok = await EmailService.sendPurchaseEmail(target, projectName);
res.json({ ok });
} catch (ex) {
res.status(500).json({ error: ex.message });
}
});
app.post('/send/reset_password', express.json(), async (req, res) => {
try {
const { target, newPassword } = req.body;
const ok = await EmailService.sendResetPasswordEmail(target, newPassword);
res.json({ ok });
} catch (ex) {
res.status(500).json({ error: ex.message });
}
});
app.post('/send/anomaly/domain', express.json(), async (req, res) => {
try {
const { target, projectName, domains } = req.body;
const ok = await EmailService.sendAnomalyDomainEmail(target, projectName, domains);
res.json({ ok });
} catch (ex) {
res.status(500).json({ error: ex.message });
}
});
app.post('/send/anomaly/visits_events', express.json(), async (req, res) => {
try {
const { target, projectName, data } = req.body;
const ok = await EmailService.sendAnomalyVisitsEventsEmail(target, projectName, data);
res.json({ ok });
} catch (ex) {
res.status(500).json({ error: ex.message });
}
});
app.post('/send/limit/50', express.json(), async (req, res) => {
try {
const { target, projectName } = req.body;
const ok = await EmailService.sendLimitEmail50(target, projectName);
res.json({ ok });
} catch (ex) {
res.status(500).json({ error: ex.message });
}
});
app.post('/send/limit/90', express.json(), async (req, res) => {
try {
const { target, projectName } = req.body;
const ok = await EmailService.sendLimitEmail90(target, projectName);
res.json({ ok });
} catch (ex) {
res.status(500).json({ error: ex.message });
}
});
app.post('/send/limit/max', express.json(), async (req, res) => {
try {
const { target, projectName } = req.body;
const ok = await EmailService.sendLimitEmailMax(target, projectName);
res.json({ ok });
} catch (ex) {
res.status(500).json({ error: ex.message });
}
});
export function start() {
const server = app.listen(PORT, () => console.log(`Listening on port ${PORT}`));
return { app, server };
}

View File

@@ -0,0 +1,23 @@
import { WELCOME_EMAIL } from '../../templates/WelcomeEmail';
import { LIMIT_50_EMAIL } from '../../templates/Limit50Email';
import { LIMIT_90_EMAIL } from '../../templates/Limit90Email';
import { LIMIT_MAX_EMAIL } from '../../templates/LimitMaxEmail';
import { PURCHASE_EMAIL } from '../../templates/PurchaseEmail';
import { ANOMALY_VISITS_EVENTS_EMAIL } from '../../templates/AnomalyUsageEmail';
import { ANOMALY_DOMAIN_EMAIL } from '../../templates/AnomalyDomainEmail';
import { CONFIRM_EMAIL } from '../../templates/ConfirmEmail';
import { RESET_PASSWORD_EMAIL } from '../../templates/ResetPasswordEmail';
export {
WELCOME_EMAIL,
LIMIT_50_EMAIL,
LIMIT_90_EMAIL,
LIMIT_MAX_EMAIL,
PURCHASE_EMAIL,
ANOMALY_VISITS_EVENTS_EMAIL,
ANOMALY_DOMAIN_EMAIL,
CONFIRM_EMAIL,
RESET_PASSWORD_EMAIL
}