Socket
Socket
Sign inDemoInstall

@netlify/plugin-emails

Package Overview
Dependencies
8
Maintainers
19
Versions
41
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.4.6-preview to 1.0.0

22

lib/handler/index.d.ts

@@ -6,7 +6,2 @@ import { Handler } from "@netlify/functions";

} | undefined;
interface IAttachment {
content: string;
filename: string;
type: string;
}
export interface IEmailRequest {

@@ -19,3 +14,2 @@ from: string;

bcc?: string;
attachments?: IAttachment[];
}

@@ -32,19 +26,3 @@ interface IEmailConfig {

}
export interface IMissingConfig {
NETLIFY_EMAILS_SECRET?: boolean;
NETLIFY_EMAILS_PROVIDER?: boolean;
NETLIFY_EMAILS_PROVIDER_API_KEY?: boolean;
NETLIFY_EMAILS_MAILGUN_HOST_REGION?: boolean;
NETLIFY_EMAILS_MAILGUN_DOMAIN?: boolean;
}
export interface IRenderRequest {
template: string;
siteId: string;
type: string;
showParameterDictionary: boolean;
parameters: {
[key: string]: string | string[];
};
}
declare const handler: Handler;
export { handler };

495

lib/handler/index.js
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {

@@ -18,3 +41,2 @@ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }

const path_1 = require("path");
const https_1 = __importDefault(require("https"));
const getEmailFromPath = (path) => {

@@ -40,103 +62,24 @@ let fileFound;

const allowedPreviewEnvironments = ["deploy-preview", "branch-deploy", "dev"];
const getMissingConfig = () => {
var _a;
const missingConfig = {};
let validConfig = true;
if (!process.env.NETLIFY_EMAILS_PROVIDER) {
missingConfig.NETLIFY_EMAILS_PROVIDER = true;
validConfig = false;
const isConfigValid = () => {
if (process.env.NETLIFY_EMAILS_PROVIDER_API_KEY === undefined) {
return false;
}
if (!process.env.NETLIFY_EMAILS_PROVIDER_API_KEY) {
missingConfig.NETLIFY_EMAILS_PROVIDER_API_KEY = true;
validConfig = false;
if (process.env.NETLIFY_EMAILS_PROVIDER === undefined) {
return false;
}
if (((_a = process.env.NETLIFY_EMAILS_PROVIDER) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === "mailgun") {
if (!process.env.NETLIFY_EMAILS_MAILGUN_HOST_REGION) {
missingConfig.NETLIFY_EMAILS_MAILGUN_HOST_REGION = true;
validConfig = false;
if (process.env.NETLIFY_EMAILS_PROVIDER === "mailgun") {
if (process.env.NETLIFY_EMAILS_MAILGUN_DOMAIN === undefined) {
return false;
}
if (!process.env.NETLIFY_EMAILS_MAILGUN_DOMAIN) {
missingConfig.NETLIFY_EMAILS_MAILGUN_DOMAIN = true;
validConfig = false;
if (process.env.NETLIFY_EMAILS_MAILGUN_HOST_REGION === undefined) {
return false;
}
}
if (!process.env.NETLIFY_EMAILS_SECRET) {
missingConfig.NETLIFY_EMAILS_SECRET = true;
validConfig = false;
}
return validConfig ? false : missingConfig;
return true;
};
const makeRenderTemplateRequest = (fileFound, parameters) => __awaiter(void 0, void 0, void 0, function* () {
const renderRequest = {
template: fileFound.file,
siteId: process.env.SITE_ID,
type: fileFound.type,
showParameterDictionary: false,
parameters,
};
return yield new Promise((resolve, reject) => {
const renderReq = https_1.default.request({
hostname: "netlify-integration-emails.netlify.app",
path: "/.netlify/functions/render",
method: "POST",
headers: {
"site-id": process.env.SITE_ID,
"Content-Type": "application/json",
},
}, (res) => {
let data = "";
res.on("data", (chunk) => {
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
data += chunk;
});
res.on("end", () => {
var _a;
const response = JSON.parse(data);
resolve(Object.assign(Object.assign({}, response), { status: (_a = res.statusCode) !== null && _a !== void 0 ? _a : 500 }));
});
});
renderReq.on("error", (error) => {
return reject(error);
});
renderReq.write(JSON.stringify(renderRequest));
renderReq.end();
});
});
const makeSendEmailRequest = (mailRequest) => __awaiter(void 0, void 0, void 0, function* () {
return yield new Promise((resolve, reject) => {
const sendReq = https_1.default.request({
hostname: "netlify-integration-emails.netlify.app",
path: "/.netlify/functions/send",
method: "POST",
headers: {
"site-id": process.env.SITE_ID,
"Content-Type": "application/json",
},
}, (res) => {
let data = "";
res.on("data", (chunk) => {
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
data += chunk;
});
res.on("end", () => {
var _a;
const response = JSON.parse(data);
const sendEmailResponse = {
message: response.message,
status: (_a = res.statusCode) !== null && _a !== void 0 ? _a : 500,
};
resolve(sendEmailResponse);
});
});
sendReq.on("error", (error) => {
return reject(error);
});
sendReq.write(JSON.stringify(mailRequest));
sendReq.end();
});
});
const handler = (event) => __awaiter(void 0, void 0, void 0, function* () {
var _a, _b, _c, _d;
var _a, _b, _c;
const { default: fetch } = yield Promise.resolve().then(() => __importStar(require("node-fetch")));
console.log(`Email handler received email request from path ${event.rawUrl}`);
const missingConfig = getMissingConfig();
const validConfig = isConfigValid();
const providerApiKey = process.env.NETLIFY_EMAILS_PROVIDER_API_KEY;

@@ -147,86 +90,42 @@ const providerName = process.env.NETLIFY_EMAILS_PROVIDER;

// If missing configuration, render preview HTML and sending missing configuration object to window varialbe
if (missingConfig) {
const missingConfigString = Object.keys(missingConfig)
.map((key) => {
if (missingConfig[key]) {
return key;
}
return "";
})
.join(", ");
console.error(`Email handler detected missing configuration: ${missingConfigString}`);
if (event.httpMethod === "POST") {
return {
statusCode: 400,
body: JSON.stringify({
message: `The emails integration is not configured correctly. We have detected the following configuration is missing: ${missingConfigString}`,
}),
};
}
if (!validConfig) {
return {
statusCode: 200,
body: `
<html>
<head>
<link rel="stylesheet" href="https://netlify-integration-emails.netlify.app/main.css">
<script>
missingConfig = ${JSON.stringify(missingConfig)}
siteId = ${JSON.stringify(process.env.SITE_ID)}
templateName = ${JSON.stringify(emailPath)}
</script>
<script defer src='https://netlify-integration-emails.netlify.app/index.js'></script>
</head>
<div id='app'></div>
</html>
`,
<html>
<head>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/highlight.min.js"></script>
<script>
hljs.highlightAll();
</script>
<link rel="stylesheet" href="https://netlify-integration-emails.netlify.app/main.css">
<script>
config = ${JSON.stringify({
NETLIFY_EMAILS_PROVIDER: providerName,
NETLIFY_EMAILS_PROVIDER_API_KEY: providerApiKey,
NETLIFY_EMAILS_MAILGUN_DOMAIN: process.env.NETLIFY_EMAILS_MAILGUN_DOMAIN,
NETLIFY_EMAILS_MAILGUN_HOST_REGION: process.env.NETLIFY_EMAILS_MAILGUN_HOST_REGION,
})}
siteId = ${JSON.stringify(process.env.SITE_ID)}
templateName = ${JSON.stringify(emailPath)}
</script>
<script defer src='https://netlify-integration-emails.netlify.app/index.js'></script>
</head>
<div id='app'></div>
</html>
`,
};
}
if (event.httpMethod === "GET") {
const showEmailPreview = allowedPreviewEnvironments.includes(process.env.CONTEXT);
if (!showEmailPreview) {
return {
statusCode: 400,
body: JSON.stringify({
message: `Email previews are not allowed in the ${process.env.CONTEXT} environment`,
}),
headers: {
"Content-Type": "text/plain",
},
};
}
if (!fs_1.default.existsSync(emailTemplatesDirectory)) {
return {
statusCode: 400,
body: JSON.stringify({
message: `Email templates directory ${emailTemplatesDirectory} does not exist`,
}),
headers: {
"Content-Type": "text/plain",
},
};
}
const showEmailPreview = allowedPreviewEnvironments.includes(process.env.CONTEXT);
if (event.httpMethod === "GET" && showEmailPreview) {
let emailTemplate;
if (emailPath !== undefined) {
// Return error if preview path is not a valid email path
if (!fs_1.default.existsSync((0, path_1.join)(emailTemplatesDirectory, emailPath))) {
console.log(`Preview path is not a valid email path - preview path received: ${emailPath}`);
return {
statusCode: 200,
body: `
<html>
<head>
<link rel="stylesheet" href="https://netlify-integration-emails.netlify.app/main.css">
<script>
missingTemplate = ${JSON.stringify(true)}
siteId = ${JSON.stringify(process.env.SITE_ID)}
templateName = ${JSON.stringify(emailPath)}
emailDirectory = ${JSON.stringify(emailTemplatesDirectory)}
</script>
<script defer src='https://netlify-integration-emails.netlify.app/index.js'></script>
</head>
<div id='app'></div>
</html>
`,
headers: {
"Content-Type": "text/html",
},
statusCode: 400,
body: JSON.stringify({
message: `Preview path is not a valid email path - preview path received: ${emailPath}`,
}),
};

@@ -236,24 +135,9 @@ }

// If no email template found, return error
if (!emailTemplate) {
if (emailTemplate === undefined) {
console.log(`No email template found for preview path - preview path received: ${emailPath}. Please ensure that an index.mjml or index.html file exists in the email template folder.`);
return {
statusCode: 200,
body: `
<html>
<head>
<link rel="stylesheet" href="https://netlify-integration-emails.netlify.app/main.css">
<script>
missingTemplate = ${JSON.stringify(true)}
siteId = ${JSON.stringify(process.env.SITE_ID)}
templateName = ${JSON.stringify(emailPath)}
emailDirectory = ${JSON.stringify(emailTemplatesDirectory)}
</script>
<script defer src='https://netlify-integration-emails.netlify.app/index.js'></script>
</head>
<div id='app'></div>
</html>
`,
headers: {
"Content-Type": "text/html",
},
statusCode: 400,
body: JSON.stringify({
message: `No email template found for preview path - preview path received: ${emailPath}`,
}),
};

@@ -264,2 +148,3 @@ }

fs_1.default.readdirSync(emailTemplatesDirectory).forEach((template) => {
// If index.html or index.mjml exists inside template folder, add to validEmailPaths
if (fs_1.default.existsSync((0, path_1.join)(emailTemplatesDirectory, template, "index.html")) ||

@@ -275,2 +160,6 @@ fs_1.default.existsSync((0, path_1.join)(emailTemplatesDirectory, template, "index.mjml"))) {

<head>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/highlight.min.js"></script>
<script>
hljs.highlightAll();
</script>
<link rel="stylesheet" href="https://netlify-integration-emails.netlify.app/main.css">

@@ -282,6 +171,8 @@ <script>

siteId = ${JSON.stringify(process.env.SITE_ID)}
siteName = ${JSON.stringify(process.env.SITE_NAME)}
provider = ${JSON.stringify(providerName)}
emailDirectory = ${JSON.stringify(emailTemplatesDirectory)}
secret = ${JSON.stringify(process.env.NETLIFY_EMAILS_SECRET)}
config = ${JSON.stringify({
NETLIFY_EMAILS_PROVIDER: providerName,
NETLIFY_EMAILS_PROVIDER_API_KEY: providerApiKey,
NETLIFY_EMAILS_MAILGUN_DOMAIN: process.env.NETLIFY_EMAILS_MAILGUN_DOMAIN,
NETLIFY_EMAILS_MAILGUN_HOST_REGION: process.env.NETLIFY_EMAILS_MAILGUN_HOST_REGION,
})}
url = ${JSON.stringify(process.env.URL)}

@@ -300,130 +191,112 @@ templateName = ${JSON.stringify(emailPath)}

}
if (event.httpMethod === "POST") {
if (!process.env.NETLIFY_EMAILS_SECRET) {
return {
statusCode: 400,
body: JSON.stringify({
message: "Secret not set in NETLIFY_EMAILS_SECRET",
}),
};
}
if (event.headers["netlify-emails-secret"] !==
process.env.NETLIFY_EMAILS_SECRET) {
return {
statusCode: 403,
body: JSON.stringify({
message: "Secret does not match",
}),
};
}
// If the email templates directory does not exist, return error
if (!fs_1.default.existsSync(emailTemplatesDirectory)) {
return {
statusCode: 404,
body: JSON.stringify({
message: `Email templates directory ${emailTemplatesDirectory} does not exist`,
}),
};
}
if (!event.body) {
return {
statusCode: 400,
body: JSON.stringify({
message: "Request body required",
}),
};
}
const requestBody = JSON.parse(event.body);
if (!requestBody.from) {
console.log("From address is required");
return {
statusCode: 400,
body: JSON.stringify({
message: "From address is required",
}),
};
}
if (!requestBody.to) {
console.log("To address is required");
return {
statusCode: 400,
body: JSON.stringify({
message: "To address is required",
}),
};
}
if (!emailPath) {
console.error(`Email path is not specified`);
return {
statusCode: 400,
body: JSON.stringify({
message: "You have not specified the email you wish to send in the URL",
}),
};
}
const fullEmailPath = `${emailTemplatesDirectory}/${emailPath}`;
const emailPathExists = fs_1.default.existsSync(fullEmailPath);
if (!emailPathExists) {
console.error(`Email path does not exist: ${fullEmailPath}`);
return {
statusCode: 404,
body: JSON.stringify({
message: `Email path ${fullEmailPath} does not exist`,
}),
};
}
const email = (0, exports.getEmailFromPath)(fullEmailPath);
if (!email) {
console.error(`No email file found in directory: ${fullEmailPath}`);
return {
statusCode: 404,
body: JSON.stringify({
message: `No email file found in directory: ${fullEmailPath}`,
}),
};
}
const renderResponseJson = yield makeRenderTemplateRequest(email, requestBody.parameters);
if ((_c = renderResponseJson.error) !== null && _c !== void 0 ? _c : !renderResponseJson.html) {
console.error(`Error rendering email template: ${JSON.stringify(renderResponseJson)}`);
return {
statusCode: renderResponseJson.status,
body: JSON.stringify({
message: `Error rendering email template${renderResponseJson.error ? `: ${renderResponseJson.error}` : ""}`,
}),
};
}
const renderedTemplate = renderResponseJson.html;
const configuration = {
providerName,
apiKey: providerApiKey,
mailgunDomain: process.env.NETLIFY_EMAILS_MAILGUN_DOMAIN,
mailgunHostRegion: process.env.NETLIFY_EMAILS_MAILGUN_HOST_REGION,
if (process.env.NETLIFY_EMAILS_SECRET === undefined ||
event.headers["netlify-emails-secret"] !== process.env.NETLIFY_EMAILS_SECRET) {
console.log("No secret provided or secret does not match");
return {
statusCode: 403,
body: JSON.stringify({
message: "Request forbidden",
}),
};
const request = {
from: requestBody.from,
to: requestBody.to,
cc: requestBody.cc,
bcc: requestBody.bcc,
subject: (_d = requestBody.subject) !== null && _d !== void 0 ? _d : "",
html: renderedTemplate,
attachments: requestBody.attachments,
}
if (event.body === null) {
return {
statusCode: 400,
body: JSON.stringify({
message: "Body required",
}),
};
const { message, status } = yield makeSendEmailRequest({
configuration,
request,
});
if (status !== 200) {
console.error(`Error sending email: ${message}`);
}
}
const requestBody = JSON.parse(event.body);
if (requestBody.from === undefined) {
console.log("From address is required");
return {
statusCode: status,
statusCode: 400,
body: JSON.stringify({
message,
message: "From address is required",
}),
};
}
if (requestBody.to === undefined) {
console.log("To address is required");
return {
statusCode: 400,
body: JSON.stringify({
message: "To address is required",
}),
};
}
if (emailPath === undefined) {
console.error(`Email path is undefined`);
return {
statusCode: 404,
body: JSON.stringify({
message: `Email path is undefined`,
}),
};
}
const fullEmailPath = `${emailTemplatesDirectory}/${emailPath}`;
const directoryExists = fs_1.default.existsSync(fullEmailPath);
if (!directoryExists) {
console.error(`Email directory does not exist: ${fullEmailPath}`);
return {
statusCode: 404,
body: JSON.stringify({
message: `Email path ${fullEmailPath} does not exist`,
}),
};
}
const email = (0, exports.getEmailFromPath)(fullEmailPath);
if (email === undefined) {
console.error(`No email file found in directory: ${fullEmailPath}`);
return {
statusCode: 404,
body: JSON.stringify({
message: `No email file found in directory: ${fullEmailPath}`,
}),
};
}
const renderResponse = yield fetch("https://netlify-integration-emails.netlify.app/.netlify/functions/render?showParamaterDictionary=true", {
method: "POST",
headers: {
"site-id": process.env.SITE_ID,
},
body: JSON.stringify({
template: email.file,
type: email.type,
parameters: requestBody.parameters,
showParamatersDictionary: false,
}),
});
const renderResponseJson = (yield renderResponse.json());
const renderedTemplate = renderResponseJson.html;
const response = yield fetch("https://test-netlify-integration-emails.netlify.app/.netlify/functions/send", {
method: "POST",
headers: {
"site-id": process.env.SITE_ID,
"content-type": "application/json",
},
body: JSON.stringify({
configuration: {
providerName,
apiKey: providerApiKey,
mailgunDomain: process.env.NETLIFY_EMAILS_MAILGUN_DOMAIN,
mailgunHostRegion: process.env.NETLIFY_EMAILS_MAILGUN_HOST_REGION,
},
request: {
from: requestBody.from,
to: requestBody.to,
cc: requestBody.cc,
bcc: requestBody.bcc,
subject: (_c = requestBody.subject) !== null && _c !== void 0 ? _c : "",
html: renderedTemplate,
attachments: requestBody.attachments,
},
}),
});
const responseText = yield response.text();
return {
statusCode: 405,
statusCode: response.status,
body: JSON.stringify({
message: "Method not allowed",
message: responseText,
}),

@@ -430,0 +303,0 @@ };

@@ -6,2 +6,3 @@ export declare const onPreBuild: ({ netlifyConfig, }: {

included_files: string[];
external_node_modules: string[];
};

@@ -8,0 +9,0 @@ };

@@ -7,2 +7,3 @@ "use strict";

exports.onPreBuild = void 0;
// import { execSync } from "child_process";
const fs_1 = __importDefault(require("fs"));

@@ -17,4 +18,15 @@ const path_1 = require("path");

else {
netlifyConfig.functions.emails = Object.assign(Object.assign({}, netlifyConfig.functions.emails), { included_files: [`${emailsDirectory}/**`] });
netlifyConfig.functions.emails = Object.assign(Object.assign({}, netlifyConfig.functions.emails), { included_files: [`${emailsDirectory}/**`], external_node_modules: ["node-fetch@2"] });
}
// const functionDependencies = ["node-fetch@2"];
// // Detect whether project is using NPM, Yarn or Pnpm
// let packageManager = "npm";
// if (fs.existsSync("yarn.lock")) {
// packageManager = "yarn";
// } else if (fs.existsSync("pnpm-lock.yaml")) {
// packageManager = "pnpm";
// }
// console.log("Installing email function dependencies");
// execSync(`${packageManager} install ${functionDependencies.join(" ")} -D`);
// console.log("Installed email function dependencies");
const emailFunctionDirectory = (0, path_1.join)(".netlify", "functions-internal", "emails");

@@ -21,0 +33,0 @@ fs_1.default.mkdirSync(emailFunctionDirectory, {

{
"name": "@netlify/plugin-emails",
"version": "0.4.6-preview",
"version": "1.0.0",
"description": "A build plugin that creates an email handler and processes requests to send emails",

@@ -5,0 +5,0 @@ "main": "./lib/index.js",

# Netlify Emails Plugin
🚧 Note: This plugin is pre-release software. Until version 1.0.0 is released, its API could change at any time.
The Netlify emails build plugin is responsible for creating a serverless function to handle email requests, using the email request to populate provided email templates and sending them using the specified email API provider.

@@ -9,3 +7,3 @@

Full documentation for the Netlify Email Integration can be found [here](https://docs.netlify.com/netlify-labs/experimental-features/email-integration).
Full documentation for the Netlify Email Integration can be found [here](https://docs.netlify.com/integrations/email-integration).

@@ -36,3 +34,3 @@ ## Supported email providers

Each email template should be stored under a folder name that represents the route of your template and the email file should be named `index.html`. E.g. `./emails/welcome/index.html`.
We support both HTML and responsive [MJML](https://mjml.io/) templates. Each email template should be stored under a folder name that represents the route of your template and the email file should be named either `index.html` or `index.mjml`. E.g. `./emails/welcome/index.html`.

@@ -54,3 +52,3 @@ If there are variables that need replacing in your email template when the email is triggered, please use the [handlebars.js](https://handlebarsjs.com/) syntax and pass the arguments in the request as shown in Step 5 below.

Visit `http://localhost:{PORT}/.netlify/functions/emails/_preview` to preview your email templates.
Visit `http://localhost:{PORT}/.netlify/functions/emails` to preview your email templates.

@@ -61,5 +59,24 @@ Please note, this preview endpoint is not made available in production and is only made available locally.

Dependent on where you would like to trigger an email being sent (on a subscribe or data request button click, when an event is triggered, etc.), add this snippet to your code that is reacting to that event.
Dependent on where you would like to trigger an email being sent (on a subscribe or data request button click, when an event is triggered, etc.), add one of the following snippets to your code that is reacting to that event.
**@netlify/emails:**
```
await sendEmail({
from: "no-reply@yourdomain.com",
to: "alexanderhamilton@test.com",
cc: "cc@test.com",
bcc: "bcc@test.com",
subject: "Welcome",
template: "welcome",
parameters: {
products: ["product1", "product2", "product3"],
name: "Alexander",
},
});
```
**node-fetch:**
```
import fetch from 'node-fetch'

@@ -66,0 +83,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc