DEV Community

Cover image for Scratch Newsletter with Appwrite
Nimit Savant
Nimit Savant

Posted on • Updated on

Scratch Newsletter with Appwrite

Introduction

Ever wondered how are newsletter systems made for product websites out there? Let's create our own newsletter system where people can come your website and subscribe to your newsletter system. Since we don't want a backend always running in the background it would an amazing idea to implement it with a functions. So let's use the one of the best Cloud Functions services out there, YES Appwrite Function.

I've already drafted an article where I've implemented Appwrite functions with TS.

Let's Start

Let's first create an UI for our website, this can be anything you want to have. I'm just make this so that I can hit on appwrite functions. You can directly skip if you've your UI already implemented.

UI Part
Step 1: I'll be using vitejs vanilla app to get a webpage quickly. You can find this on their website to scaffold an application
yarn create vite my-vue-app --template vanilla
Enter fullscreen mode Exit fullscreen mode

Step 2: Remove all the unnecessary css and js code. I remove counter.js and this is how my other files look like
main.js

import './style.css';

document.querySelector('#app').innerHTML = `
  <div>
    <section>
      <div class="newsletter-box">
        <h2>Scratch Newsletter</h2>
        <form>
          <input type="text" placeholder="Email" />
          <button>Subscribe</button>
        </form>
      </div>
    </section>
  </div>
`
Enter fullscreen mode Exit fullscreen mode

style.css

html {
    background-color: blanchedalmond;
    font-family: 'Courier New', Courier, monospace;
}

.newsletter-box {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background-color: #f4f4f4;
    padding: 20px;
    margin: 20px 0;
    border-radius: 5px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

.newsletter-box h2 {
    margin-bottom: 20px;
}

.newsletter-box button {
    background-color: #ff6600;
    color: white;
    padding: 10px 20px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
}

.newsletter-box button:hover {
    background-color: #ff5500;
}   

.newsletter-box input {
    width: 50%;
    padding: 10px;
    margin-bottom: 10px;
    border: 1px solid #ccc;
    border-radius: 5px;
}
Enter fullscreen mode Exit fullscreen mode

index.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Scratch Newsletter</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/main.js"></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Getting started with Appwrite

Database

  1. Create a collection in your database Creating collection in Appwrite console

Collection view

  1. Now let's add attributes, we just need the email in our case

Attributes section in Appwrite Console

Attribute added for email

  1. Create an API key for your function, this is needed because your function is a basically a backend application which needs access to your database and other appwrite features like accounts, storage and more.

a. Now go to your overview tab and click on Create API key

Overview screen creating API key

Create API key screen

b. Add permissions just for documents.write. Always keep your permissions to the minimal. This would help you to keep your Appwrite function more secure and also give an idea what features you're actually going to use.

adding scopes for api key

c. Copy the API key we're going to use it further to add it in our function settings

Creating functions

  1. There are many ways to create a function in appwrite but I like to use the CLI. So you can do the installation by following the installation on this link
  2. Then go into your project. And scaffold the project with the below command
appwrite init project
Enter fullscreen mode Exit fullscreen mode
appwrite init function ## I've used nodejs 18 for my settings
Enter fullscreen mode Exit fullscreen mode
  1. Once you've installed the project upload it to your appwrite console
appwrite deploy function
Enter fullscreen mode Exit fullscreen mode

Amazing we're half way through, now we will move on to actually add code to your appwrite function.

App password

So basically now that we're using Gmail as our client to send emails, we've to use it's app passwords feature to get a passwords. It's pretty intuitive, you just go to your 2factor auth -> turn it on and scroll below to add app passwords. For more information follow this article

App passwords screen on Gmail

Appwrite Functions Code

Code explaining the main driver function code please comments explaining what is happening in function, I've also added jsdocs for this function so that you can have a level of typing when you're developing the app.

Why is this important?
Well when you're prototyping your function you would want to see in your editor the obvious mistakes so that you won't have to upload multiple times to check for dumb errors which can be sorted in the editor.

Code explanation

Basically here the sendEmail() function helps us creating a connection to our GMAIL SMTP server which we would further use to send emails. We're using app password here that we generated in the above step and would keep that in our .env our further steps. And then our driver code is taking the email from our post call which in-turn first adds the email to our collection that we created in the above steps and then would call the sendEmail(). The call would be triggered from our client app but you can use the Appwrite console to test this out too!

Appwrite console showing how to trigger a function

functions/Scratch Newsletter/src/main.js

import { Client, Databases, ID } from 'node-appwrite';
import nodemailer from 'nodemailer';

/**
 * @param {string} toEmail 
 * @returns 
 */
async function sendEmail(toEmail, log) {

  // Create a new transporter
  var transporter = nodemailer.createTransport({
      host: "smtp.gmail.com",
      port: 587,
      secure: false,
      auth: {
          user: 'mnimitsavant@gmail.com',
          pass: process.env.GMAIL_APP_PASSWORD,
      }
  });

  // Send the email object
  var message = {
    from: "mnimitsavant@gmail.com",
    to: toEmail,
    subject: "test email",
    html: "<h1>Welcome to Scratch Newsletter</h1><br /><p>Thank you for signing up with us!</p>",
  };

  // Send the email
  const info = await transporter.sendMail(message);

  // Log the message id
  log(`Message sent ${info.messageId}`);

  return info.messageId;
}

/**
 * @typedef {object} Req
 * @property {string} method
 * @property {JSON} body
 * @property {String} body.email
 * @property {object} query
 */


/**
 * @typedef {object} Res
 * @property {Function} json
 * @property {Function} send
 * @property {Function} empty
 */

/**
 * @param {object} context
 * @param {Req} context.req
 * @param {Res} context.res
 * @param {object} context.log
 * @param {object} context.error
 * 
 */
export default async ({ req, res, log, error }) => {
  // Why not try the Appwrite SDK?
  //

  var headers = {};
  headers['Content-Type'] = 'application/json';
  headers['Access-Control-Allow-Origin'] = '*';
  headers["Access-Control-Allow-Headers"] =  "Content-Type";
  headers['RAccess-Control-Allow-Methods'] = 'RANDOM';

  log('Scratch Newsletter function invoked')
  try {
    // Create connection with Appwrite Client

    // This was important to add because the websites will have cors, so first they'll send a small request to check if the server is accepting the request and then the post call will happen
    if(req.method === 'OPTIONS') {
      log('OPTIONS METHOD')
      return res.send('', 204, headers);
    } else if(req.method === 'POST') {
      log('Request method is POST')

      const client = new Client()
        .setEndpoint('https://cloud.appwrite.io/v1')
        .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)
        .setKey(process.env.APPWRITE_API_KEY);


        // Get the request body and parse
        const body = req.body;
        if(!body.email) {
          // Send a response
          log('Email is required')
          return res.send('Email is required', 400, headers);
        }

        // Create a new database instance
        const database = new Databases(client);

        // Create a new document in the database
        const newData = await database.createDocument(process.env.APPWRITE_DATABASE_ID, process.env.APPWRITE_COLLECTION_ID, ID.unique(), {
          email: body.email
        });

        // Log the new data
        log(newData);
        if(newData.$id) {
          // Send new email
          await sendEmail(newData.email, log);

          // Send a response
          return res.send('Subscribed to the newsletter', 201, headers);

        } else {
          // Send a response
          return res.send('Failed to subscribe to the newsletter', 409, headers);
        }
      } else {
        // Send a response when not using POST method
        return res.send('Invalid request method. Please use POST method to subscribe to the newsletter.', 405, headers);
      }

  } catch (err) {
    // Log the error
    error(err.toString());
    return res.send('Some error occurred. Please try again later.');
  }
};
Enter fullscreen mode Exit fullscreen mode

Add secrets to our Appwrite console

Go to out function settings and then go to environment variable section and click on editor and add your respective .env.

Appwrite Console adding secrets

Edit our Client App to hit our function

Step 1.We add a function setupSubscribeButton which helps to hit our function.

client app main.js

import './style.css';

const setupSubscribeButton = (element) => {

  console.log('Setting up subscribe button');
  element.addEventListener('submit', async(event) => {
    event.preventDefault();
    const email = event.target.elements.email.value;
    if(email === '' || email === null) {
      alert('Email is required');
      return;
    }
    console.log('Subscribing', email);

    const res = await fetch('https://65f335d0163e397c95de.appwrite.global', {
      method: 'POST',
      headers: {
        'content-type': 'application/json',
      },
      body: JSON.stringify({ 
        email: email
      }),
    });

    console.log('Subscribed', res.status);
  });
}

document.querySelector('#app').innerHTML = `
  <div>
    <section>
      <div class="newsletter-box">
        <h2>Scratch Newsletter</h2>
        <form action="" id="subscribe">
          <input type="text" id="email" name="email" placeholder="Email" />
          <button type="submit" onClick()=>Subscribe</button>
        </form>
      </div>
    </section>
  </div>
`

setupSubscribeButton(document.querySelector('#subscribe'));
Enter fullscreen mode Exit fullscreen mode

Congratulations

Client app adding email

Email in your mail box

Scratch Newsletter

A newsletter implementation from scratch using Appwrite Function with Js

Tech stack used

  • Appwrite
  • Node.js

Architecture

  • Every time someone adds their email to the newsletter on the static page. The form sends an invocation to the Appwrite function.
  • This appwrite function then adds the email to the collection in the appwrite database and sends them a welcome email.

Author






Socials

Twitter: https://twitter.com/SavantNimit
LinkedIn: https://www.linkedin.com/in/nimitsavant
Twitch: https://www.twitch.tv/nimit2801
Website: https://nimitsavant.me

More with Appwrite

Top comments (1)

Collapse
 
roohafza profile image
Hardik Raval

Amazing loved it ✨