Integrate Sendgrid using Web API in Ruby on Rails - React application
Tags: webdev, react, ruby, rails
Published: 2022-11-20 23:29:48

Sometimes it's always challenging to integrate a mailing system in Rails API for beginners. In this blog, I'll explain step by step on how to integrate Sendgrid in a Ruby on Rails-React application. The blog is beginner friendly and easy to understand. 
Setting up Sendgrid mailing system in Rails has always scared beginners in Rails as they think its a complex process. Just to let you know, there is nothing complex with setting up Sendgrid in Rails application. 
There are two ways of setting up Sendgrid email system in a web application, that is using Sendgrid Web API and Sendgrid SMTP Relay. In this blog, I will discuss setting up Sendgrid using Web API which is the most recommended way. Let's begin the journey.

## Create a Rails application
Lets begin by creating up our rails application. Use the `rails new sendgrid --api` to create a API rails application.
![create rails app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/49v0h028w72v5paawkqx.png) 
Get into the direcory of the rails application using the command `cd sendgrid`. Genrate a user resource with the username and email as the attributes. Both are strings. The command is `rails g resource user username email`.
![rails g resource](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/76o04ip75i85hah4xlcs.png). Run `rails db:migrate` to run the migrations. 
![Rails db:migrate](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sncp575jyom3gwkig7u6.png). Start the rails server using the command `rails s`.
![rails s](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7m4xzr0m7qe51qvcynpn.png)
With that done, we shall have our routes, controller, and models already created. Let's go ahead and add some codes in the user controller and user model. 
### Add Validations in user model
Let's add some validations to make sure the input fields are not empty.
```ruby
class User < ApplicationRecord
    validates :username, presence: true 
    validates :email, presence: true 
end

```
### Add controller actions in user controller
We are done with the user model, let's now modify our `user_controller` file to create a new user and validate if username or email is empty. Head to the controllers folder and edit the `user_controller`.rb file. Let's first create our index method that we shall use to test our API to see if we have our data in properly stored. Edit the userController class to look this this way

```ruby
class UsersController < ApplicationController
    # Get all users 
    def index 
        users= User.all 
        render json: users
    end
end
```
Let's add a create method to add a user to the database

```ruby
# Create a user 
    def create 
        @user = User.create!(user_params)
        render json: {body: @user, message: "User created successfully"}, status: :created
    end
```
We need to create two private methods, one for permitting user parameters and the other for validating user input.

```ruby
 # Private methods
    private 

    def user_params
        params.permit(:username, :email)
    end

    # Invalid entity response 
    def invalid_entry(invalid)
        render json: {errors: invalid.record.errors.full_messages}, status: :unprocessable_entity 
    end
```
Then add a rescue for the invalid user entity at the top of the class

```ruby
rescue_from ActiveRecord::RecordInvalid, with: :invalid_entry
```
Our user controller class should be looking similar to this at the moment

```ruby
class UsersController < ApplicationController
    rescue_from ActiveRecord::RecordInvalid, with: :invalid_entry
    # Get all users
    def index 
        users= User.all 
        render json: users
    end

    # Create a user 
    def create 
        @user = User.create!(user_params)
        render json: {body: @user, message: "User created successfully"}, status: :created
    end

    # Private methods
    private 

    def user_params
        params.permit(:username, :email)
    end

    # Invalid entity response 
    def invalid_entry(invalid)
        render json: {errors: invalid.record.errors.full_messages}, status: :unprocessable_entity 
    end
end
```
Let's have our user controller looking that way at the moment. We shall come back to edit it when setting up Sendgrid. 
Next, let's add CORS by uncommenting the code in cors.rb in initializers folder in config directory. The code in cors.rb should look like this 

```ruby
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins "*"

    resource "*",
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end
```
To make sure our cors configuration is working, we need to uncomment the cors gem in gem file and the run `bundle install` to install the necessary dependencies. Restart the rails server.  
## Test APIs using postman 
We need to test our APIs and see if they are working perfectly. Open postman and try creating a new user using the post method, and then get all the users created using the get method. 
* Create a user

![API test postmant](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uzfqvkvo96tktljhvmz2.png)
* Get all users

![API test postmant](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/musbroafgd4nrwqk2ywv.png)
Okay, our API endpoints are working fine. 
Now, let's get our front-end working
## Create React App 
The next step is creating a react application with a user simple user signup page. Let's begin by creating our react application. Use the command `npx create-react-app client` to create a react app called client ![create react app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mg8t1w87u7xiwfu5v551.png). Get in the react app directory using the command `cd client` and run `npm start` to run the react application. 
### Create a sign up page
Create a Signup.js file in the clients src folder. Create a functional component called Signup and add the following code in it. 

```javascript
import React from 'react'

function Signup() {
  return (
    <div style={{width: "300px", marginLeft: "auto", marginRight: "auto"}}>
        <h2>User Signup</h2>
        <form style={{display: "flex", flexDirection: "column"}}>
            <label htmlFor='username'>Username:</label> 
            <input type={'text'} id= "username" name='username' /><br/>
            <label htmlFor='email'>Email:</label> 
            <input type={'text'} id= "email" name='email' /><br/>
            <button type='submit'>Submit</button>
        </form>
    </div>
  )
}

export default Signup
```
Edit the App.js with the following code

```javascript
import './App.css';
import Signup from './Signup';

function App() {
  return (
    <div className="App">
      <Signup />
    </div>
  );
}

export default App;
```
Add the functionalities on Signup.js to enable the form to send data to the server using the API endpoint.

```javascript
import React, { useState } from 'react'

function Signup() {
  const [data, setData] = useState({
    username: '',
    email: '',
  })
  const [errors, setErrors] = useState([])
  const [message, setMessage] = useState('')
  const [success, setSuccess] = useState(false)

  function handleChange(e){
    let name = e.target.name 
    let value = e.target.value 
    setData({...data, [name]: value})
  }

  // POST user data
  function handleSubmit(e){
    e.preventDefault()
    fetch('http://localhost:3000/users',{
        method: "POST",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify(data)
    }).then(res=>{
        if(res.ok){
            res.json().then(user=>{
                setMessage(user.message)
                setSuccess(true)
            })
        }else{
            res.json().then(err=>setErrors(err.errors))
        }
    })
  }
  return (
    <div style={{width: "300px", marginLeft: "auto", marginRight: "auto"}}>
        <h2>User Signup</h2>
        {success? <h3 style={{color: "green"}}>{message}</h3>:
        <div>
            {errors.map(err=><p key={err} style={{color: "red"}}>{err}</p>)}
        </div>}
        <form style={{display: "flex", flexDirection: "column"}} onSubmit={handleSubmit}>
            <label htmlFor='username'>Username:</label> 
            <input type={'text'} id= "username" name='username' onChange={handleChange} /><br/>
            <label htmlFor='email'>Email:</label> 
            <input type={'text'} id= "email" name='email' onChange={handleChange}/><br/>
            <button type='submit'>Submit</button>
        </form>
    </div>
  )
}

export default Signup
```
## Set up sendgrid 
First, you need to create an account with sendgrid. Fill in all the details required while setting the account. Wait for your account to be verified. **NOTE: Any user can create the account even if you don't have a company or organization**. Once your account has been verified and you can view your dashboard, on the left menu, click on email API and the Integration guide. Your dashboard will display two options for email set up. Use Web API and click choose to select it. 
![Web API select](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nkk4455p8jy5me8bqz2d.png).
Next, select Ruby as your language 

![Select Ruby](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0dxfbsk0hifr9sfdnpev.png)
Enter the name of your API key and click create API to create the key. Follow the instructions on creating the environment variable that shall store the API key. Create the environment variable in the root of your rails application. `echo "export SENDGRID_API_KEY='YOUR API KEY'" > sendgrid.env`
Add the file to the git ignore so that it is not sent to your github account. This is done to avoid exposing your API key which on exposure, may lead to suspension of your Sendgrid account. `echo "sendgrid.env" >> .gitignore`
Set the source `source ./sendgrid.env`. We will later move this API key to a credentials.yml.enc to keep it safe for use.
Next, add the sendgrid gem, `gem 'sendgrid-ruby'` to the gem file and run `bundler` or `bundle install` to install the dependencies. Restart the rails server. Copy the code given under **send your first email** into a ruby file in your application. Replace the sample details given with your details and then try and run the file separately on the terminal using the command, `ruby filename.rb`. You will get an email sent to the "to" email. The next step is just moving the code to a class and reusing the class where necessary.
### Add the API key into the credentials.yml.enc file
We have come a long way and we are almost there, hang in tight, don't let go. The credentials.yml.enc file (Located in the config directory) is encrypted and therefore cannot be edited directly. To edit the file, type the following command on the terminal `EDITOR="code --wait" rails credentials:edit`. Code basically represents the code editor you are using. I'm using VS code thus setting the editor to code. You can read more about editing this file on [rails Custom Credentials](https://guides.rubyonrails.org/security.html#custom-credentials) or [stackoverflow](https://stackoverflow.com/questions/52370065/issue-to-open-credentials-file). When the file opens on your code edit in the edit mode, add the sendgrid API key as follows: `sendgrid: YOUR API KEY`. The API key should not be in quotes. Save and exit from the file to save changes.
### Create the email_service.rb file

I'll have the email file created in a services folder in the apps folder. Create services folder and add in an email_service.rb file. Create a class matching the file name as it is a convention in ruby. in the class, create a class method called class and pass in the parameters; from, to, subject, and content. Then copy the code from sendgrid and edit it as follows. Your class should look like this. 

```ruby
require 'sendgrid-ruby'
include SendGrid
class EmailService 
    def self.call from:, to:, subject:, content:
        from = Email.new(email: from)
        to = Email.new(email: to)
        subject = subject
        content = Content.new(type: 'text/plain', value: content)
        mail = SendGrid::Mail.new(from, subject, to, content)

        sg = SendGrid::API.new(api_key: Rails.application.credentials.sendgrid)
        response = sg.client.mail._('send').post(request_body: mail.to_json)
        puts response.status_code
        puts response.body
        puts response.headers
        puts Rails.application.credentials.sendgrid
    end
end

```
***Note the following  changes from the code***

- This code has been changed to get the API key set in the credentials.yml.enc file `sg = SendGrid::API.new(api_key: Rails.application.credentials.sendgrid)`
- The code on `mail = SendGrid::Mail.new(from, subject, to, content)` may slightly look different from the one provided on sendgrid account. The code from sendgrid looks like this `mail = Mail.new(from, subject, to, content)`. I made the changes since the code from sendgrid, when I moved it to the class, it was showing an error about missing parameters. Changing the code to look as the one above solved the problem. 
***Edit the user_controller to send mail on user signup***
The last step, edit the user controller by adding the email service. We are using the EmailService class to send the email. We call the `call` class method of EmailService on the class and pass in the arguments. Edit the code in the create method to look as follows.

```ruby

def create 
        @user = User.create!(user_params)
        @from = "useyouremail@gmail.com"
        @subject = "New User Account"
        @content = "Thank you for registering with us #{@user.username}. Your account has been created successfully"
        EmailService.call(from: @from, to: @user.email, subject: @subject, content: @content)
        render json: {body: @user, message: "User created successfully"}, status: :created
    end
```
Your UserController class should look like the one below

```ruby
class UsersController < ApplicationController
    rescue_from ActiveRecord::RecordInvalid, with: :invalid_entry
    # Get all users
    def index 
        users= User.all 
        render json: users
    end

    # Create a user 
    def create 
        @user = User.create!(user_params)
        @from = "useyouremail@gmail.com"
        @subject = "New User Account"
        @content = "Thank you for registering with us #{@user.username}. Your account has been created successfully"
        EmailService.call(from: @from, to: @user.email, subject: @subject, content: @content)
        render json: {body: @user, message: "User created successfully"}, status: :created
    end

    # Private methods
    private 

    def user_params
        params.permit(:username, :email)
    end

    # Invalid entity response 
    def invalid_entry(invalid)
        render json: {errors: invalid.record.errors.full_messages}, status: :unprocessable_entity 
    end
end
```
Try and register a user with a valid email and check the rails server if the status printed on the terminal is 200s, which means an email has been sent successfully. 
![status code 202](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mkt265yl07quh5ul7mw6.png)
User Signup

![sample user registration](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cdhbbfb1oxzgydaxaag8.jpg)
Signup email sent

![sent email](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qclzvuaa4j9y2lg5qbef.png)
[Github repo link](https://github.com/chibukasia/sendgrid-setup)
That's it, bye and Happy Coding!
![bye](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d3kr1j09xlailw1h0z9e.gif)