Scratch Newsletter with Appwrite
Tags: appwritehack, javascript, newsletter, tutorial
Published: 2024-03-15 18:15:29

{% collapsible 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. {% endcollapsible %}



> I've already drafted an [article](https://dev.to/nimit2801/appwrite-functions-with-typescript-459f) 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.
{% collapsible 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](https://vitejs.dev/guide/#scaffolding-your-first-vite-project)
```cli
yarn create vite my-vue-app --template vanilla
```

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
```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>
`
```
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;
}
```
index.html
```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>
```
{% endcollapsible %}

### Getting started with Appwrite

#### Database

1. Create a collection in your database
![Creating collection in Appwrite console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n3cpyg3y8qa727euzswk.png)

![Collection view](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6dc4hwxq5at5i11p6fm6.png)

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

![Attributes section in Appwrite Console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fs0yzq5w6bd6x7jzkd5k.png)

![Attribute added for email](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/70gxwii5un130dnx8ab5.png)

3. 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vjf49v69dkasyk46k9wc.png)

![Create API key screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1k49k95q1yqvyxl27fbg.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ugysgp0710t1qlo0bj1l.png)

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](https://appwrite.io/docs/tooling/command-line/installation)
2. Then go into your project. And scaffold the project with the below command 
```cli
appwrite init project
```
```cli
appwrite init function ## I've used nodejs 18 for my settings
```
3. Once you've installed the project upload it to your appwrite console
```cli
appwrite deploy function
```

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](https://support.google.com/accounts/answer/185833?hl=en&sjid=7252417775763453554-AP)

![App passwords screen on Gmail](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w10vauqfd0fdg9hndkom.png)

#### 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. 

{% details 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/96106t46yvmm215uxn7u.png)


{% enddetails %}

functions/Scratch Newsletter/src/main.js
```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.');
  }
};
```

### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gjnha8jdlwfuyh3splgq.png)


### 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
```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'));
```

## Congratulations


![Client app adding email](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g0ypzj8epp3o2nxnzchx.png)



![Email in your mail box](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/36ee23bv2tquvyz7u1dy.png)

{% embed https://github.com/nimit2801/scratch-newsletter/ %}

## 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

{% embed  https://dev.to/nimit2801/appwrite-functions-with-typescript-459f
 %}