DEV Community

Cover image for Create a Private Email API for Free
Steeve
Steeve

Posted on

Create a Private Email API for Free

This article covers how to create your own secured Email API for free, with all the code explained.

The origin of this project came because I wanted to make websites really fast to load worldwide, almost instantaneously (< 30ms). The problem is that:

  • Each of my website has a server for sending emails when the contact form is filled.
  • Servers are slow and located in one (or two) place on the planet earth.
  • Replicating Server is a pain to manage and expensive.

My strategies to solve that issues was to:

  • Keep only the front-end and upload it to CDN: the website is pre-built in pure HTML/CSS/JS, and it is now available worldwide. No more servers. This article does not cover this subject.
  • Make an independent Email API server : Anytime a contact form is filled from one or multiple website, it will request the Email API, and emails will be sent 🎉 This is what we are going to talk about :D

👋 Meet Email API

The Email API is open-source on Github, made with NodeJS: https://github.com/steevepay/email-api. The code is super simple with one API endpoint POST /send. Here is the detailed flow to send an Email:
Flow chart to send a mail with the Mailer API through HTTP requests

  1. Make an HTTP request to the Email API from a website or server.
  2. Each HTTP request is verified: if the domain origin is part of the CORS whitelist, or an Access key is provided on the Authorization header, the request is accepted, otherwise it returns the 401 status code.
  3. The Email API connects to the SMTP server (of your choice), then it send the email.

Get your SMTP credentials

The Email API connects to an SMTP server to send mails, that's why you need SMTP credentials. Many SMTP servers are available for free, choose the service your prefer:

  • Brevo : Send maximum 300 emails/day
  • Smtp2go : Send 1,000 emails/month
  • Mailjet : Send 6000 emails/month
  • Google SMTP: If you have a Google Workspace subscription, you can access a quota of emails per months.
  • OVHCloud: When you buy a domain and an hosting plan, OVH gives you a free SMTP server for one or multiple emails addresses.
  • Proton Mail: If you have a Proton subscription, you can access SMTP credentials
  • Non-exhaustive list, do your own research...

Email API Setup

Before going further, make sure you have installed: Git, NodeJS, and optionally Docker.

  • First clone the following repository:
git clone https://github.com/steevepay/email-api.git
Enter fullscreen mode Exit fullscreen mode
  • Go to the email-api directory:
cd email-api
Enter fullscreen mode Exit fullscreen mode
  • Create an .env file with the following configuration, and add your SMTP credentials:
# SERVER
DOMAIN="mailer.domain.com"
PORT=3000
# SECURITY WHITELIST (OPTIONAL): multiple domains are accepted if it is seperated with a coma, such as `website1.org,website2.org`
WHITELIST="domain.org"
# SECURITY ACCESS KEY (OPTIONAL)
AUTHORIZATION_KEY=""
# SMTP CONFIG
SMTP_HOST=""
SMTP_PORT=""
SMTP_FROM=""
SMTP_PASS=""
Enter fullscreen mode Exit fullscreen mode
  • Install NPM packages with npm install
  • To start the server:
# With NPM Command Line:
npm run start
# With Docker Compose:
docker-compose up
Enter fullscreen mode Exit fullscreen mode

To send an email, make an HTTP request to endpoint POST /send, you must provide the following body as JSON:

{
  "from": "",
  "to": "",
  "subject": "",
  "text": "",
  "html": "",
}
Enter fullscreen mode Exit fullscreen mode

The from is the email sender, and to is the email receiver. The content of the email must be provided as html and raw text.

If everything goes right, the response will return OK with the status code 200.

If something goes wrong, it will return the following responses:

  • 401: Unauthorized, the origin domain is probably not part of the CORS whitelist, or you did not provide the Access Key
  • 404: The page you are requesting is not correct
  • 405: The body of the HTTP request is not a JSON, or malformed, or it is missing an attribute.
  • 500: The server has an issue, check logs, probably your SMTP credentials are not correct, or the email failed to be delivered.

Stack and Code Break-down

Only three files are required to start the API:

index.js
mailer.js
.env
Enter fullscreen mode Exit fullscreen mode

On the index.js, environment variables are loaded, then we can find the API routing powered with Express. Here is the /send request:

app.use(express.json());
app.options('/send', cors(corsOptionsDelegate)) // enable pre-flight request for POST request
app.post(
  "/send",
  cors(corsOptionsDelegate),
  body("from").optional().isEmail().normalizeEmail(),
  body("to").optional().isEmail().normalizeEmail(),
  body("cc").optional().isEmail().normalizeEmail(),
  body("subject")
    .optional()
    .not()
    .isEmpty()
    .trim()
    .escape()
    .isLength({ min: 2 }),
  body("html").optional().not().isEmpty().trim().escape().isLength({ min: 2 }),
  body("text").optional().not().isEmpty().trim().escape().isLength({ min: 2 }),
  (req, res, next) => {
    // Finds the validation errors in this request and wraps them in an object with handy functions
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    return next();
  },
  mailer.sendEmail
);
Enter fullscreen mode Exit fullscreen mode

Notice each body parameter is verified with the Node module express-validator.

The CORS npm module is loaded as middleware, and checks before each HTTP request if the Origin domain of the request is part of the whitelist:

// Configure CORS
var whitelist = process.env?.WHITELIST && process.env?.WHITELIST !== '' ? process.env.WHITELIST?.split(',') : [];
if (whitelist.length > 0) {
    console.log("CORS Whitelist: ", whitelist)
}
var corsOptionsDelegate = function (req, callback) {
  if (whitelist.indexOf(req.header('Origin')) === -1 && req.header('authorization') !== process.env.AUTHORIZATION_KEY) {
    return callback(new Error("Not allowed by CORS"));
  } 
  return callback(null, true);
}
Enter fullscreen mode Exit fullscreen mode

The middleware also accept an Access Key, and must be provided through the header Authorization.

If the request configuration is good, the last middleware mailer.sendEmail is executed to send the email. The function mailer.sendEmail is located in the file mailer.js:

const nodemailer = require('nodemailer')

sendEmail = function (req, res) {
  const _cc =  [];

  if (req.body.cc) {
    _cc.push(req.body.cc);
  }
  if (process.env.SMTP_CC) {
    _cc.push(process.env.SMTP_CC);
  }
  if (req.body.from && req.body.to && req.body.subject && req.body.text && req.body.html) {
    if (req.body?.cc) {
      _cc.push(req.body.cc);
    }
    if (process.env.SMTP_CC) {
      _cc.push(process.env.SMTP_CC);
    }
    return deliver({
      from: req.body.from,
      to: req.body.to,
      cc: _cc,
      subject: `${req.body.subject}`,
      text: req.body.text, 
      html: req.body.html
    }, res);
  } else {
    return res.sendStatus(405); // Method Not Allowed
  }
}


function deliver(message, res) {
  const transporter = nodemailer.createTransport({
    host: process.env.SMTP_HOST,
    port: process.env.SMTP_PORT,
    secure: true, // true for 465, false for other ports
    auth: {
      user: process.env.SMTP_FROM,
      pass: process.env.SMTP_PASS
    },
    logger: true
  })

  transporter.sendMail(message, (error, info) => {
    if (error) {
      // eslint-disable-next-line no-console
      console.error("🔴 Send Email Error:", error.toString());
      res.sendStatus(500)
    } else {
      res.sendStatus(200)
    }
  })
}

module.exports = {
  sendEmail
}
Enter fullscreen mode Exit fullscreen mode

The Nodemailer module is used to connect to the SMTP server through the nodemailer.createTransport function, then the email is sent with transporter.sendMail. At the beginning of the function, each parameter of the mail is verified. If something is missing, the status code 405 is returned.

Conclusion

Now multiple websites can request the same API: The service is mutualised, and that's cheap and easy to maintain.

Big downsides to consider:

  • You can send emails only from one address, the one linked to your SMTP credentials.
  • To send emails from a new website, you must add the website domain to the CORS whitelist, that requires a reboot to load new environment variables.
  • The API supports only one single Access Key, if you want to update it, you must edit the .env file and reboot the server.

Thanks for reading! cheers 🍻

Top comments (6)

Collapse
 
stankukucka profile image
Stan Kukučka

@steeve this is cool. Good for SSG websites.

Collapse
 
steeve profile image
Steeve

Thank Stan! Yes, I have 4 SSG websites hosted on CDN, all using the Email API server for 2 years now: it works great for SEO, loading time, almost no maintenance, and cost nothing.

Collapse
 
stankukucka profile image
Stan Kukučka

@steeve I would love to try it. But not clear what to implement on the website side (SSG). The Github repo seems to explain how to run the server only.

Thread Thread
 
steeve profile image
Steeve

On the website side, you just have to make a POST HTTP request to the API server, for instance:

 await fetch('https://mailerapi.domain.com/send', {
     method: 'POST',
     headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json'
     },
     body: JSON.stringify({
         from: "email-sender@domain",
         to: "email-receiver@domain",
         subject: "Email subject",
         text: "Content of the email as text",
         html: "Content of the email as HTML"
    })
})
Enter fullscreen mode Exit fullscreen mode

Notes:

  • For creating the content of the Email, I searched Email template online, then I adapted with JSFiddle to preview the result.
  • The from is the email address linked to the SMTP server
  • The to could be your personal email address
Collapse
 
salika_dave profile image
Salika Dave

Nicely done 🙌

Collapse
 
steeve profile image
Steeve

Thank you Salika 🙌