How to Send Emails in Rust With SendGrid

March 22, 2023
Written by
Reviewed by

https://flickr.com/photos/renaissancechambara/3122900531/in/photolist-5KXFwB-9wZVB4-h7PXz-43hmvP-BFuR7-aCWzjv-aotqvm-5bfc1r-bCgbnP-bCQnXf-aNsyve-avjqFW-aqmp65-asomH4-5HpZRJ-aNsy4Z-aCKJ7b-ajTxY-37tn7-anwQvG-amGqnY-avugGe-avjqeW-bo2fU6-aCAVHS-duaiKs-9MwBvg

If you're coding in Node.js, Python, C#, PHP, Java, Go, or Ruby, then sending an email using SendGrid is pretty trivial. But what about Rust, one of the newest and most popular languages?

Sadly, there isn't a helper library available for it – yet. So, in this tutorial, I'm going to show you the essentials of sending an email using SendGrid's API with Rust.

Prerequisites

To follow along with this tutorial, you're going to need the following:

Set up a new Rust project

The next thing to do is to create a new Cargo package, to save some time and effort in writing the necessary code. To do that and change into the new package (directory), run the following commands.

cargo new send-email-with-sendgrid
cd send-email-with-sendgrid

Add the required Crates

Next, to avoid writing too much code, you're going to need a few crates (external dependencies); these are:

  • dotenv: This crate merges variables defined in .env into the application’s environment variables provided by the operating system. Among other things, this ensures that secure credentials, such as your SendGrid API key, are kept out of code
  • error-chain: This crate simplifies the use of Rust's error handling functionality
  • http: This crate contains common HTTP types
  • reqwest: This crate provides a convenient, higher-level HTTP Client
  • serde and serde_json: These crates, collectively, serialise responses from the Twilio API into a series of structs, making them easier to work with, programmatically

To add them to the project, along with the required features, run the following commands.

cargo add dotenv error-chain http reqwest serde serde_json
cargo add reqwest --features reqwest/json --features reqwest/blocking
cargo add serde --features derive

Set the required environment variables

The next thing you need to do is to set 5 environment variables. These are a combination of your SendGrid API key, so that you can make authenticated calls to SendGrid's API, and a sender and recipient for the email; both full name and email address.

Create a new file in the project's top-level directory, named .env, and in that file add the following configuration.

SENDGRID_API_KEY="xxxxx"
SENDER_NAME="xxxxx"
SENDER_EMAIL="xxxxx"
RECIPIENT_NAME="xxxxx"
RECIPIENT_EMAIL="xxxxx"

Now, you need to create and retrieve a SendGrid API key, if you don't already have one. To do that, log in to your SendGrid account and under Settings > API Keys click Create API Key.

Form to create a SendGrid API key. There"s a text field asking for an API key name, and radio buttons to select the permission for the key.

In the form that appears, enter a meaningful name for the key in the API Key Name field and click Create & View.

SendGrid API key created.

The SendGrid API key will now be visible. Copy and paste it into .env in place of the <<SENDGRID_API_KEY>> placeholder.

Be aware that you will only be able to view the API key once. If you navigate away from the page or close the tab/window and haven't copied the API key, you will have to create a new one.

The sender identity section of the SendGrid dashboard

Then, in the left-hand side navigation menu, click Settings > Sender Authentication. There, copy one of the email addresses from the Single Sender Verification section, and paste it into .env in place of the placeholder for SENDER_EMAIL.

Then, replace the placeholder for SENDER_NAME with the sender's full name. After that, replace the RECIPIENT_NAME and RECIPIENT_EMAIL placeholders with the name and address of the email's recipient.

Write the Rust code

Now, let's write the Rust code! Replace the existing contents of src/main.rs with the code below.

use dotenv;
use http::StatusCode;
use reqwest::{blocking::Client, Error};
use reqwest::header;
use serde_json::json;
use std::env;

struct User {
    name: String,
    email: String,
}

fn main() -> Result<(), Error> {
    dotenv::dotenv().ok();

    let api_key = env::var("SENDGRID_API_KEY").unwrap();

    let sender = User {
        name: String::from(env::var("SENDER_NAME").unwrap()),
        email: String::from(env::var("SENDER_EMAIL").unwrap()),
    };

    let recipient = User {
        name: String::from(env::var("RECIPIENT_NAME").unwrap()),
        email: String::from(env::var("RECIPIENT_EMAIL").unwrap()),
    };

    let body = json!(
        {
            "personalizations": [{
                "from": {
                    "email": sender.email,
                    "name": sender.name
                },
                "to": [{
                    "email": recipient.email,
                    "name": recipient.name
                }]
            }],
            "from": {
                "email": sender.email,
                "name": sender.name
            },
            "subject": "Let's Send an Email With Rust and SendGrid",
            "content": [
                {
                    "type": "text/plain",
                    "value": "Here is your AMAZING email!"
                },
                {
                    "type": "text/html",
                    "value": "Here is your <strong>AMAZING</strong> email!"
                },
            ]
        }
    );

    let client = Client::new()
        .post("https://api.sendgrid.com/v3/mail/send")
        .json(&body)
        .bearer_auth(api_key)
        .header(
            header::CONTENT_TYPE, 
            header::HeaderValue::from_static("application/json")
        );

    let response = client.send()?;

    match response.status() {
        StatusCode::OK | StatusCode::CREATED | StatusCode::ACCEPTED => println!("Email sent!"),
        _ => eprintln!(
            "Unable to send your email. Status code was: {}. Body content was: {:?}",
            response.status(),
            response.text()
        ),
    }

    Ok(())
}

The code starts by defining a small Struct, User, to simplistically model an email sender and recipient. Then, the main() function is defined. It starts off by:

  • Loading all of the environment variables defined in .env;
  • Initialising the API key (api_key) to authenticate against SendGrid's API, and the email's sender (sender) and recipient (recipient), from the five environment variables, defined earlier in .env.

Following that, using Serde JSON's json macro, the request's body (body) is defined. It stores the email's sender, recipient, subject, and the plain text and HTML versions of the email's body.

You can find the entire list of supported properties in the documentation.

Then, a new Reqwest Client object (client) is defined, which handles making the call to SendGrid's API to send the email. On that object, it tells it to make a POST request to https://api.sendgrid.com/v3/mail/send, with a JSON body using the contents of body.

The request uses the bearer_auth() method to set a Bearer token to successfully authenticate against SendGrid's API, and sets the response's accepted content-type to application/json.

The request is then sent, with the response being stored in response. The response's status code is checked to see if it was an HTTP 200 (OK), 201 (Created), or 202 (Accepted). If so, the user is notified that the email was successfully queued to be sent by SendGrid.

If the status code was not one of these three values (such as an HTTP 400 (Bad Request) or 404 (Not Found)) the response's body is printed to the terminal, as it contains the details about why the email was not accepted to be sent.

Test that the application works

With all of the code and configuration in place and Crates installed, it's time to check that the code works. To do that, run the following command in the top-level directory of the project.

cargo run

All being well, you should see the crates being installed followed by Email sent! printed to the terminal. A little while later, the email should arrive in your inbox, which you can see below.

The received email in Apple Mail

That's how to send emails in Rust with SendGrid

There's not a lot to it, but I thought that, as an avid Rust user, it was worth stepping through all the same. How would you have implemented the solution? Share your thoughts with me on LinkedIn.

Matthew Setter is a PHP Editor in the Twilio Voices team and a PHP, Go, and Rust developer. He’s also the author of Mezzio Essentials and Docker Essentials. When he's not writing PHP code, he's editing great PHP articles here at Twilio. You can find him at msetter[at]twilio.com, on Twitter, and on GitHub.

"Ruscombe ... postbox." by bazzadarambler is licensed under CC BY 2.0.