Skip to content

Commit

Permalink
feat: integrate mailer service directly in mailpress
Browse files Browse the repository at this point in the history
  • Loading branch information
schettn committed Jun 6, 2024
1 parent c7418b5 commit aef66f2
Show file tree
Hide file tree
Showing 11 changed files with 13,965 additions and 250 deletions.
13,869 changes: 13,767 additions & 102 deletions .pylon/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .pylon/index.js.map

Large diffs are not rendered by default.

Binary file modified bun.lockb
Binary file not shown.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,15 @@
"dotenv": "^16.0.3",
"html-minifier": "^4.0.0",
"html-to-text": "^9.0.5",
"nodemailer": "^6.9.13",
"nodemailer-plugin-inline-base64": "^2.1.1",
"prisma": "^5.2.0",
"twig": "^1.16.0"
},
"devDependencies": {
"@types/html-minifier": "^4.0.2",
"@types/html-to-text": "^9.0.4",
"@types/nodemailer": "^6.4.15",
"@types/twig": "^1.12.9",
"commitizen": "^4.2.5",
"git-cz": "^4.9.0",
Expand Down
16 changes: 0 additions & 16 deletions src/clients/mailer/src/index.ts

This file was deleted.

70 changes: 0 additions & 70 deletions src/clients/mailer/src/schema.generated.ts

This file was deleted.

107 changes: 46 additions & 61 deletions src/services/mail-factory.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { ServiceError, getContext, logger, requireAuth } from "@cronitio/pylon";
import { htmlToText } from "html-to-text";
import { EmailTemplate } from "../repository/models/EmailTemplate";

import service from "..";
import { Email } from "../repository/models/Email";
import { EmailTemplate } from "../repository/models/EmailTemplate";
import { EmailTemplateFactory } from "../services/email-template-factory";
import { sendMail as sendMailAzure } from "../services/mailer/azure";
import { sendMail as sendMailGoogle } from "../services/mailer/google";
import { sendMail as sendMailSMTP } from "../services/mailer/smtp";
import { executeInSandbox } from "../services/transformer-sandbox";
import { sq } from "../clients/mailer/src";
import service from "..";

export class MailFactory {
private static async send(
Expand All @@ -27,30 +30,24 @@ export class MailFactory {
);
}

const [data, errors] = await sq.mutate((m) =>
m.sendMailSMTP({
mailOptions: {
from: senderEmail.email,
to: envelope.to,
replyTo: envelope.replyTo,
subject: envelope.subject,
html: bodyHTML,
text: body,
},
smtpOptions: {
host: smtpConfig.host,
port: smtpConfig.port,
secure: smtpConfig.secure,
user: smtpConfig.username,
password: smtpConfig.password,
},
})
const data = await sendMailSMTP(
{
from: senderEmail.email,
to: envelope.to,
replyTo: envelope.replyTo,
subject: envelope.subject,
html: bodyHTML,
text: body,
},
{
host: smtpConfig.host,
port: smtpConfig.port,
secure: smtpConfig.secure,
user: smtpConfig.username,
password: smtpConfig.password,
}
);

if (errors) {
throw new Error(errors[0].message);
}

return data;
}

Expand All @@ -66,48 +63,36 @@ export class MailFactory {
const token = await oauthConfig.$freshAccessToken();

if (oauthConfig.provider === "AZURE") {
const [data, errors] = await sq.mutate((m) =>
m.sendMailAzure({
mailOptions: {
from: senderEmail.email,
to: envelope.to,
replyTo: envelope.replyTo,
subject: envelope.subject,
html: bodyHTML,
text: body,
},
oauthOptions: {
accessToken: token,
},
})
const data = await sendMailAzure(
{
from: senderEmail.email,
to: envelope.to,
replyTo: envelope.replyTo,
subject: envelope.subject,
html: bodyHTML,
text: body,
},
{
accessToken: token,
}
);

if (errors) {
throw new Error(errors[0].message);
}

return data;
} else if (oauthConfig.provider === "GOOGLE") {
const [data, errors] = await sq.mutate((m) =>
m.sendMailGoogle({
mailOptions: {
from: senderEmail.email,
to: envelope.to,
replyTo: envelope.replyTo,
subject: envelope.subject,
html: bodyHTML,
text: body,
},
oauthOptions: {
accessToken: token,
},
})
const data = await sendMailGoogle(
{
from: senderEmail.email,
to: envelope.to,
replyTo: envelope.replyTo,
subject: envelope.subject,
html: bodyHTML,
text: body,
},
{
accessToken: token,
}
);

if (errors) {
throw new Error(errors[0].message);
}

return data;
}
} else {
Expand Down
46 changes: 46 additions & 0 deletions src/services/mailer/azure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { MailOptions, OAuthOptions } from "./types";

export async function sendMail(
mailOptions: MailOptions,
oauthOptions: OAuthOptions
): Promise<object> {
const apiUrl = "https://graph.microsoft.com/v1.0/me/sendMail";

const headers = {
Authorization: `Bearer ${oauthOptions.accessToken}`,
"Content-Type": "application/json",
};

const requestBody = JSON.stringify({
message: {
subject: mailOptions.subject,
from: { emailAddress: { address: mailOptions.from } },
body: {
contentType: mailOptions.html ? "html" : "text",
content: mailOptions.html || mailOptions.text,
},
toRecipients: mailOptions.to.map((to) => ({
emailAddress: { address: to },
})),
...(mailOptions.replyTo && {
replyTo: [{ emailAddress: { address: mailOptions.replyTo } }],
}),
},
saveToSentItems: true,
});

console.log(requestBody);

const response = await fetch(apiUrl, {
method: "POST",
headers: headers,
body: requestBody,
});

if (!response.ok) {
const error = await response.json();
throw new Error(`Failed to send email: ${error.error.message}`);
}

return response;
}
39 changes: 39 additions & 0 deletions src/services/mailer/google.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { MailOptions, OAuthOptions } from "./types";

export async function sendMail(
mailOptions: MailOptions,
oauthOptions: OAuthOptions
): Promise<object> {
const apiUrl = "https://gmail.googleapis.com/gmail/v1/users/me/messages/send";

const headers = {
Authorization: `Bearer ${oauthOptions.accessToken}`,
"Content-Type": "application/json",
};

const message =
`From: ${mailOptions.from}\n` +
`To: ${mailOptions.to.join(",")}\n` +
`Subject: ${mailOptions.subject}\n\n` +
`${mailOptions.html || mailOptions.text}`;

const requestBody = JSON.stringify({
raw: Buffer.from(message)
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_"),
});

const response = await fetch(apiUrl, {
method: "POST",
headers: headers,
body: requestBody,
});

if (!response.ok) {
const error = await response.json();
throw new Error(`Failed to send email: ${error.error.message}`);
}

return response;
}
51 changes: 51 additions & 0 deletions src/services/mailer/smtp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import nodemailer from "nodemailer";
import inlineBase64 from "nodemailer-plugin-inline-base64";
import { SentMessageInfo } from "nodemailer/lib/smtp-connection";
import { MailOptions } from "./types";

export interface SMTPOptions {
host: string;
port: number;
secure: boolean;
user: string;
password: string;
}

async function createConnection({
host,
port,
secure,
user,
password,
}: SMTPOptions) {
return new Promise<nodemailer.Transporter>((resolve, reject) => {
resolve(
nodemailer.createTransport({
host: host,
port,
secure: secure,
auth: {
user: user,
pass: password,
},
})
);
});
}

// SEND MAIL
export async function sendMail(
mailOptions: MailOptions,
smtpOptions: SMTPOptions
): Promise<SentMessageInfo> {
const connection = await createConnection(smtpOptions);
connection.use("compile", inlineBase64({ cidPrefix: "snek_" }));

console.log(mailOptions);

const mail = await connection.sendMail(mailOptions);

console.log(mail);

return mail;
}
12 changes: 12 additions & 0 deletions src/services/mailer/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export interface MailOptions {
from: string;
to: string[];
replyTo?: string;
subject: string;
text: string;
html?: string;
}

export interface OAuthOptions {
accessToken: string;
}

0 comments on commit aef66f2

Please sign in to comment.