Using SMS and Email Services in Strapi
Tags: strapi, twilio, react, javascript
Published: 2022-08-19 08:04:00

_This article explains how to create and use SMS and email services in Strapi._

**Author: @codev206**

Being in a restaurant, the first thing that gets presented to you is the menu. This action repeatedly happens whenever you go to a restaurant. In the same vein, so many events or activities get repeated in software engineering. Therefore, it is nice that we employ the DRY (Don't Repeat Yourself) concept all the time to make things easier.

## Prerequisites

To follow this article, you’ll need:

- A Twilio Account (for SMS service)
- Node.js (Version 14 and later)
- A Gmail Account (for emails)

## Controllers in Strapi

These two concepts relate. Controllers are where actions are stored. These actions get triggered when a client requests a particular route defined in the code. Controllers are responsible for controlling the flow of any application that follows the `MVC framework,` including Strapi.

## Services in Strapi

Services help you with the DRY principle, as they do what they even mean; they serve. These are reusable functions that simplify controllers' logic.

Whenever you create a new content type or model, Strapi generates a new service file that does nothing but could override the generic service created in the `node_module.`

Let's spin up a project by creating a new application if you don't have one already.

```bash
    npx create-strapi-app@latest my-project --quickstart
    //Or
    yarn create strapi-app my-project --quickstart
```

After installation, navigate to `http://localhost:1337/admin` and complete the form to create the first Administrator user.


## Creating Services

First, we will create an API with its configurations, controller, and service.
```bash
    npm run strapi generate
```

Then do the following.

- Select `api` as the generator.
- Enter `comment` for the name.
- This API is not for a plugin, select `n.`

Your selections should look like the screenshot below:

![Creating Comment API](https://i.imgur.com/NJHqm8V.png)


Next, generate a content-type with the Strapi *generate* command below:
```bash
    npm run strapi generate
```
We want this content type to have two fields: **user** and **description**. So your selection should look like the screenshot below:

![Creating Comment Content-type](https://i.imgur.com/kXLbyCL.png)


Your codebase should look like this:

![Comment Schema](https://i.imgur.com/mUrS3qQ.png)


The above command will create an empty collection called Comments.

![Viewing Comment collection](https://i.imgur.com/DOS3dAu.png)


We want to use the Service Strapi generated to send SMS when a user creates a new comment. However, we can achieve endless possibilities of functionalities with the Strapi Generated Services.

## Using Services to Send SMS

Create a file called `sms.js` in the `./api/comment/services` folder and add paste this code to it:

```js
    'use strict';
    module.exports = {};
```

We will send an SMS whenever a user creates a comment using Twilio. Let's install Twilio using the following command:

### Copying Your Twilio Credentials
[Log in to Your Twilio Account or create one if you don't already have it here](https://www.twilio.com/try-twilio). Now, copy out your `ACCOUNT SID` and `AUTH TOKEN.`


![Getting Twilio Credentials](https://i.imgur.com/08nRwBZ.png)


Paste the following in the `.env` file located in `./env`:

    TWILIO_ACCOUNT_SID = AC82a29b91a67xxxxxxxxx
    TWILIO_AUTH_TOKEN = 81682479468249xxxxxxxxxxx
    MYNUM = +23490xxxxxxx
    TWILIONUM  = +16463xxxxxx

Where `AC82a29b91a67xxxxxxxxx` is your exact `ACCOUNT SID` and `81682479468249xxxxxxxxxxx` is the same `AUTH TOKEN` you copied from your Twilio account. `TWILIONUM` will be the exact phone number given by Twilio, and `MYNUM` should be the destination number.

Then we will create a function that will be exported and be globally accessible through the `strapi.services`.

In our service file at `./api/comment/services/sms.js`:
```js
    module.exports = {
      sendSms() {
        const accountSid = process.env.TWILIO_ACCOUNT_SID;
        const authToken = process.env.TWILIO_AUTH_TOKEN;
        const myNum = process.env.MYNUM;
        const twilioNum = process.env.TWILIONUM;
        const client = require("twilio")(accountSid, authToken);
        client.messages
          .create({
            body: "Hello Admin, someone just posted a comment",
            from: twilioNum, //the phone number provided by Twillio
            to: myNum, // your own phone number
          })
          .then((message) => console.log(message.sid));
      },
    };
```

### Triggering the SMS Services
Now let us go to the `./api/comment/controllers/comment.js` and define what will happen whenever a user comments on our application.

In our `./api/comment/controllers/comment.js`, we will call the global `strapi.services` and other methods we created in `./api/comment/services/sms.js.`

```js
    module.exports = {
        async create(ctx) {
        strapi.service("api::comment.sms").sendSms();
        return await strapi
          .service("api::comment.comment")
          .create(ctx.request.body);
        },
    
        async get(ctx) {
        return await strapi
          .service("api::comment.comment")
          .get(ctx.request.body);
      },
    }
```

Whenever we make a post request in Comments collections, it calls the Customs Service, which communicates to the Twilio server and sends us an SMS. Now create the `create` service in `./api/comment/service/comment.js` to save the actual comment to our collection.

```js
    "use strict"
    module.exports = () => ({
      async create(data) {
        return await strapi.entityService.create("api::comment.comment", {
          data,
        });
      },
    
       async get() {
        return await strapi.entityService.findMany("api::comment.comment");
      },
    });

Finally, configure a route for our `create` service in `./api/comment/routes/comment.js` with the code snippet below:


    module.exports = {
      routes: [
        {
          method: "POST",
          path: "/comment",
          handler: "comment.create",
          config: {
            policies: [],
            middlewares: [],
          },
        },
        {
          method: "GET",
          path: "/comment",
          handler: "comment.get",
          config: {
            policies: [],
            middlewares: [],
          },
        },
      ],
    };
```

### Creating a New Comment with Postman
We can test whether or not the SMS will be delivered when we try to create a new comment by making a post request. Make sure you have the create access in your application role by navigating to `Settings->USERS & PERMISSIONS PLUGIN->Roles-> Public`:

![Creating access permission](https://i.imgur.com/u2jDoy0.png)


So we will be using [Postman](https://www.postman.com/) to send a `POST` request to this URL http://localhost:1337/comments. Fill in the following JSON data in the request body and hit the Send button.
```json
    {"user": "Precious",
    "description": "I just want to comment that Strapi is dope"}
```
![testing with Postman](https://i.imgur.com/wrQcuoG.png)


You should also receive the SMS delivered to your phone number.

## Using Services to Send Emails

Next, we will talk about how to send emails using custom services. We will try to notify ourselves when a product is added to the collection. We should get notified through email.

Let's create a new API for that:

```bash
    npm run strapi generate
```
![Creating Emails API](https://i.imgur.com/RDV0yZt.png)


The command will create a new folder in `./api/` called `product` with the following sub-folders routes, `services` `controllers.` We will use a package called `nodemailer.` So make sure you install it using the command below.

```bash
    npm install nodemailer
```

### Creating the Product Collection
Let's create another collection for our product API with the generate command.
```bash
    npm run strapi generate
```
![Creating Email Content-type](https://i.imgur.com/JHmwMoW.png)


Now, paste the following codes in the service of our just created product found in `./api/product/services/product.js.`

```js
    const toEmail = process.env.TOEMAIL;
    const welcome = process.env.WELCOME;
    module.exports = {
      async create(data) {
        const response = await strapi.entityService.create("api::product.product", {
          data,
        });
        strapi
          .service("api::comment.sendmail")
          .send(
            welcome,
            toEmail,
            "Welcome",
            `A product has been created ${entity.name}`
          );
        return response;
      },
    };
```

Next, create a controller for the `create` service in the `./api/product/controllers/product.js` file with the code below:

```js
    module.exports = {
      async create(ctx) {
        return await strapi
          .service("api::prooduct.prooduct")
          .create(ctx.request.body);
      },
    };
```
Then configure the route in the `./api/product/routes/product.js` file with the code below:
```js
    module.exports = {
      routes: [
        {
         method: 'POST',
         path: '/product',
         handler: 'product.create',
         config: {
           policies: [],
           middlewares: [],
         },
        },
      ],
    };
```

Ensure you have the create access in your application role in the product(`Settings->USERS & PERMISSIONS PLUGIN->Roles-> Public`). And of course, all your environment variable (`TOEMAIL` and `WELCOME`) are defined in the `.env` file.

Create a`sendmail.js` file in `./api/sendmail/services/` and add the code below:
```js
    const nodemailer = require('nodemailer');
    const userEmail = process.env.MYEMAIL
    const userPass = process.env.MYPASS
    // Create reusable transporter object using SMTP transport.
    const transporter = nodemailer.createTransport({
      service: 'Gmail',
      auth: {
        user: userEmail,
        pass: userPass,
      },
    });
    module.exports = {
      send: (from, to, subject, text) => {
        // Setup e-mail data.
        const options = {
          from,
          to,
          subject,
          text,
        };
        // Return a promise of the function that sends the email.
        return transporter.sendMail(options);
    
      },
    };
```

Also, define all your environment variables (`MYEMAIL` and `MYPASS`) in the `.env` file.
This is your Gmail email address and the password to access it. Unfortunately, for our app to have access to our email, we need to lessen the security of Gmail a little bit. This is because Google does not let third-party applications access its accounts without approvals.

**Go to your Google Accounts and put ON less secure app access.**

![Enabling Less secure app access](https://i.imgur.com/Ux0PZLu.png)


Next, create a controller for our product API's `create` service.
Now we will create a new product in Postman(HTTP Client). Send a `Post` request to this URL http://localhost:1337/products. Add the JSON data below to the request body:
```json
{
"name": "A headphone",
"price": 2000
}
```
![Testing Email endpoint](https://i.imgur.com/0857JJh.png)


When you hit the Send button, you should get this response below if everything goes successfully:

    {
        "id": 5,
        "name": "A headphone",
        "price": 2000,
        "createdAt": "2022-05-05T12:23:09.965Z",
        "updatedAt": "2022-05-05T12:23:09.965Z"
    }

You should also get a notification on your email like below if everything goes successfully:

![Confirming Email sent](https://i.imgur.com/6bUMALF.png)


This Email Notification task is just a tip of what you can achieve with Strapi Services. The use case of Services is limitless. You can do any business logic.

## Building a Commenting App

Now, what's this whole concept of services in Strapi without an actual example of how it works? So I will be using Reactjs to show you one of the many ways services in Strapi work. Let's move away from our current Strapi project. Instead, we will create a new application with create-react-app.

In a different directory, run this command to create a new React app:
```bash
    npx create-react-app strapicomment
```
I decided to call my application strapicomment ( you can call yours anything). After our react application has been created, let's move into its directory and start the application.
```bash
    cd strapicomment
    yarn start
```
The above command will set up our React application, and it will start on http://localhost:3000/.

![Starting React Project](https://i.imgur.com/TPrAFMV.png)


Next, open the codebase in any code editor of your choice. I will be using VSCode for this example:

![Project structure](https://i.imgur.com/fium7kV.png)


## Cleaning it up
We will clean up the project and remove some unnecessary codes with React Quickstart boilerplate. In the src folder, delete the logo.svg, and create a folder called components (which is where all our components will be going).

Next, copy and paste this code to replace the existing code in `App.js`:
```js
    function App() {
      return (
        <div className="App">
          <h1>Hello React</h1>
        </div>
      );
    }
    export default App;
```
Let’s create three components in `.src/components` namely Form.js, `List.jsx`, and `Comment.jsx` In our `Form.js`, paste in the following codes.
```js
    import React, { Component } from "react";
    export default class Form extends Component {
      constructor(props) {
        super(props);
        this.state = {
          loading: false,
          error: "",
          comment: {
            user: "",
            description: ""
          }
        };
        // bind context to methods
        this.handleFieldChange = this.handleFieldChange.bind(this);
        this.onSubmit = this.onSubmit.bind(this);
      }
      /**
       * Handle form input field changes & update the state
       */
      handleFieldChange = event => {
        const { value, name } = event.target;
        this.setState({
          ...this.state,
          comment: {
            ...this.state.comment,
            [name]: value
          }
        });
      };
      /**
       * Form submit handler
       */
      onSubmit(el) {
        // prevent default form submission
        el.preventDefault();
        if (!this.isFormValid()) {
          this.setState({ error: "All fields are required." });
          return;
        }
        // loading status and clear error
        this.setState({ error: "", loading: true });
        // persist the comments on server
        let { comment } = this.state;
        fetch("http://localhost:1337/api/comment", {
          headers:{'Content-type':'application/json'},
          method: "post",
          body: JSON.stringify(comment)
        })
          .then(res => res.json())
          .then(res => {
            if (res.error) {
              this.setState({ loading: false, error: res.error });
            } else {
              this.props.addComment(comment);
    
              this.setState({
                loading: false,
                comment: { ...comment, description: "" }
              });
            }
          })
          .catch(err => {
            this.setState({
              error: "yo! something is sideways",
              loading: false
            });
          });
      }
      /**
       * Simple validation
       */
      isFormValid() {
        return this.state.comment.user !== "" && this.state.comment.description !== "";
      }
      renderError() {
        return this.state.error ? (
          <div className="alert alert-danger">{this.state.error}</div>
        ) : null;
      }
      render() {
        return (
          <React.Fragment>
            <form method="post" onSubmit={this.onSubmit}>
              <div className="form-group">
                <input
                  onChange={this.handleFieldChange}
                  value={this.state.comment.user}
                  className="form-control"
                  placeholder="UserName"
                  name="user"
                  type="text"
                />
              </div>
              <div className="form-group">
                <textarea
                  onChange={this.handleFieldChange}
                  value={this.state.comment.description}
                  className="form-control"
                  placeholder="Your Comment"
                  name="description"
                  rows="5"
                />
              </div>
              {this.renderError()}
              <div className="form-group">
                <button disabled={this.state.loading} className="btn btn-primary">
                  Comment &#10148;
                </button>
              </div>
            </form>
          </React.Fragment>
        );
      }
    }
```
I am using bootstrap for basic styling. I decided to bring it in through CDN, so go to the public folder in your root and locate `index.html` and paste this in between your head tags:
```html
     <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" 
    integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" 
    crossorigin="anonymous">
```
In our `List.jsx`, paste in the following codes.
```js
    import React from "react";
    import Comment from "./Comment";
    export default function List(props) {
      return (
        <div className="commentList">
          <h5 className="text-muted mb-4">
            <span className="badge badge-success">{props.comments.length}</span>{" "}
            Comment{props.comments.length > 0 ? "s" : ""}
          </h5>
          {props.comments.length === 0 && !props.loading ? (
            <div className="alert text-center alert-info">
              Be the first to comment
            </div>
          ) : null}
          {props.comments.map((comment, index) => (
            <Comment key={index} comment={comment} />
          ))}
        </div>
      );
    }
```
What we are doing here is mapping through and displaying the available comments. If there is none, you will be the first to comment.
In our `Comment.jsx`, paste in the following codes.
```js
    import React from "react";
    export default function Comment(props) {
      const { user, description } = props.comment;
      return (
        <div className="media mb-3">
          <div className="media-body p-2 shadow-sm rounded bg-light border">
            <h6 className="mt-0 mb-1 text-muted">{user}</h6>
            {description}
          </div>
        </div>
      );
    }
```
Back to `App.js` in the src folder, replace it with the codes below.
```js
    import React, { Component } from "react";
    import List from "./components/List";
    import Form from "./components/Form";
    class App extends Component {
      constructor(props) {
        super(props);
        this.state = {
          comments: [],
          loading: false
        };
        this.addComment = this.addComment.bind(this);
      }
      componentDidMount() {
        // loading
        this.setState({ loading: true });
        // get all the comments
        fetch("http://localhost:1337/api/comment")
          .then(res => res.json())
          .then(res => {
            this.setState({
              comments: res,
              loading: false
            });
          })
          .catch(err => {
            this.setState({ loading: false });
          });
      }
    
      addComment(comment) {
        this.setState({
          loading: false,
          comments: [comment, ...this.state.comments]
        });
      }
      render() {
    
        return (
          <div className="App container bg-light shadow">
    
            <div className="row">
              <div className="col-4  pt-3 border-right">
                <h6>Speak your Truth</h6>
                <Form addComment={this.addComment} />
              </div>
              <div className="col-8  pt-3 bg-white">
                <List
                  loading={this.state.loading}
                  comments={this.state.comments}
                />
              </div>
            </div>
          </div>
        );
      }
    }
    export default App;
```
We have successfully created our application. Whenever a user comments, we get notified through SMS. We can do the same with email or any functionality across our minds.

![Sample](https://i.imgur.com/gCFVDhx.png)

## Github Links

[The code for both the React App and the Strapi backend is available here.](https://github.com/icode247/strapi-sms-email)

## Conclusion

Strapi services offer a whole lot of benefits, and that makes developing easy. We have seen how this works in the little application that sends SMS using Twillio API whenever a user comments on our application. We have also seen how to create email notifications with Strapi Services.