Skip to content

Commit

Permalink
Add contact function
Browse files Browse the repository at this point in the history
HTTP function to deliver email sent from a website's contact form.
  • Loading branch information
zuzust committed Jan 31, 2018
1 parent 7076e7a commit 65dbeab
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 12 deletions.
154 changes: 145 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
'use strict';

const cors = require('cors');
const nodemailer = require('nodemailer');
const bunyan = require('bunyan');

const config = require('./config.json');


/**
* Check Google Cloud Functions status
*
* Trigger this function by making a GET request to:
* https://[YOUR_REGION].[YOUR_PROJECT_ID].cloudfunctions.net/ping
*
* @example
* curl -X GET "https://us-central1.your-project-id.cloudfunctions.net/ping"
*
* @param {object} request Cloud Function request context.
* @param {object} response Cloud Function response context.
*/
exports.ping = (request, response) => {
// Everything is ok
response.status(200).send('Pong!');
};

/**
* Returns a nodemailer transporter.
*
* @returns {object} Transporter object.
*/
function getTransporter() {
const logger = bunyan.createLogger({ name: 'nodemailer' });
Expand All @@ -30,17 +42,141 @@ function getTransporter() {
}

/**
* Check your email provider status
*
* Trigger this function by making a GET request to:
* https://[YOUR_REGION].[YOUR_PROJECT_ID].cloudfunctions.net/check
*
* @example
* curl -X GET "https://us-central1.your-project-id.cloudfunctions.net/check"
*
* @param {object} request Cloud Function request context.
* @param {object} response Cloud Function response context.
*/
exports.check = (request, response) => {
const transporter = getTransporter();

transporter.verify((error, success) => {
if (error) {
console.log(`Error: ${error}`);
response.status(400).send('Error: Server is not ready to accept messages');
} else {
return Promise.resolve()
.then(_ => {
const transporter = getTransporter();
return transporter.verify();
})
.then(_ => {
response.status(200).send('Success: Server ready to take our messages');
}
});
})
.catch(err => {
console.log(`[Error] { code: ${err.code}, message: ${err.message} }`);
response.status(400).send('Error: Server is not ready to accept messages');
});
}

/**
* Gets the email body data from the HTTP request body.
*
* @param {object} requestBody The request payload.
* @param {string} requestBody.botTrap Field to prevent spam.
* @param {string} requestBody.name Name of the sender.
* @param {string} requestBody.email Email address of the sender.
* @param {string} requestBody.message Body of the email message.
* @returns {object} Payload object.
*/
function getPayload(requestBody) {
if (requestBody.botTrap) {
const error = new Error('Spam not allowed!');
error.code = 400;
throw error;
} else if (!requestBody.name) {
const error = new Error('Name not provided. Make sure you have a "name" property in your request');
error.code = 400;
throw error;
} else if (!requestBody.email) {
const error = new Error('Email address not provided. Make sure you have an "email" property in your request');
error.code = 400;
throw error;
} else if (!requestBody.message) {
const error = new Error('Email content not provided. Make sure you have a "message" property in your request');
error.code = 400;
throw error;
}

return {
name: requestBody.name,
email: requestBody.email,
message: requestBody.message
};
}

/**
* Ask nodemailer to send an email using your email provider.
*
* @param {object} req Cloud Function request context.
* @param {object} req.body The request payload.
* @param {object} res Cloud Function response context.
*/
function fcontact(req, res) {
return Promise.resolve()
.then(_ => {
const payload = getPayload(req.body);
const message = {
// email address of the sender
from: config.envelope.sender,
// comma separated list or an array of recipients email addresses
to: config.envelope.recipient,
// subject of the email
subject: config.envelope.subject,
// email address that will appear on the Reply-To: field
replyTo: payload.email,
// plaintext body
text: `
Formulario de contacto:
Nombre: ${payload.name}
Email: ${payload.email}
${payload.message}
`,
// HTML body
html: `
<h3>Formulario de contacto</h3>
<p>
<b>Nombre</b>: ${payload.name}<br>
<b>Email</b>: ${payload.email}
</p>
<p>${payload.message}</p>
`,
};
return message;
})
.then(message => {
const transporter = getTransporter();
return transporter.sendMail(message);
})
.then(_ => {
// console.log(req.body);
// Send number of emails sent successfully
res.status(200).send("1");
})
.catch(err => {
console.log(`[Error] { code: ${err.code}, message: ${err.message} }`);
res.status(400).send(`Error: ${err.message}`);
});
};

/**
* Send an email using your email provider.
*
* Trigger this function by making a POST request with a payload to:
* https://[YOUR_REGION].[YOUR_PROJECT_ID].cloudfunctions.net/contact
*
* @example
* curl -X POST "https://us-central1.your-project-id.cloudfunctions.net/contact" --data '{"name":"Jane Doe","email":"jane.doe@email.com","message":"Hello World!"}'
*
* @param {object} request Cloud Function request context.
* @param {object} response Cloud Function response context.
*/
exports.contact = (request, response) => {
const fcors = cors({
origin: true,
methods: ['POST', 'OPTIONS']
});

fcors(request, response, () => fcontact(request, response));
};
19 changes: 19 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
},
"dependencies": {
"bunyan": "^1.8.12",
"cors": "^2.8.4",
"nodemailer": "^4.4.2",
"serverless-google-cloudfunctions": "^1.1.0"
}
Expand Down
8 changes: 5 additions & 3 deletions serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,11 @@ provider:
memorySize: 256
timeout: 60s
project: ${file(./config.json):project_id}
# the path to the credentials file needs to be absolute
credentials: ${file(./config.json):credentials_file}

plugins:
- serverless-google-cloudfunctions

# needs more granular excluding in production as only the serverless provider npm
# package should be excluded (and not the whole node_modules directory)
package:
exclude:
- node_modules/**
Expand All @@ -33,4 +30,9 @@ functions:
events:
- http: path

contact:
handler: contact
events:
- http: path

frameworkVersion: "=1.25.0"

0 comments on commit 65dbeab

Please sign in to comment.