Sendgrid

Sendgrid is a popular provider for sending email with a visual email template builder. When the email-sendgrid feature is enabled, Roadster will initialize a Sendgrid client via the sendgrid crate.

Sendgrid also supports sending emails via SMTP. For details on Roadster's SMTP integration, see the previous chapter.

Configure the Sendgrid integration

The Sendgrid connection details can be configured via your app's config files, and via env vars or an AsyncSource for sensitive connection details.

# Note: Hard-coded connection details are used here for demonstration purposes only. In a real application,
# an `AsyncSource` should be used to fetch secrets from an external service, such as AWS or GCS
# secrets manager services.

[email.sendgrid]
api-key = "api-key"
# `sandbox` should not be true in prod (simply omit this field in the prod config)
sandbox = true

Sending an email

With the Sendgrid client, emails are sent by providing a Sendgrid email template ID and the template's parameters. Below is a simple example of using the Sendgrid client. See Sendgrid's docs for more details on the other fields you may want to set when sending emails.

pub struct EmailConfirmationSendgrid {
    state: AppContext,
}

impl EmailConfirmationSendgrid {
    pub fn new(state: &AppContext) -> Self {
        Self {
            state: state.clone(),
        }
    }
}

#[derive(Debug, TypedBuilder, Serialize, Deserialize)]
pub struct EmailConfirmationSendgridArgs {
    user_id: Uuid,
}

#[async_trait]
impl Worker<EmailConfirmationSendgridArgs> for EmailConfirmationSendgrid {
    #[instrument(skip_all)]
    async fn perform(&self, args: EmailConfirmationSendgridArgs) -> sidekiq::Result<()> {
        let user = User::find_by_id(&self.state, args.user_id).await?;

        send_email(&self.state, &user).await?;

        Ok(())
    }
}

const TEMPLATE_ID: &str = "template-id";

#[derive(Serialize)]
struct EmailTemplateArgs {
    verify_url: String,
}

/// Send the verification email to the user.
async fn send_email(state: &AppContext, user: &User) -> RoadsterResult<()> {
    let verify_url = "https://exaple.com?verify=1234".to_string();

    let personalization = Personalization::new(Email::new(&user.email))
        .set_subject("Please confirm your email address")
        .add_dynamic_template_data_json(&EmailTemplateArgs { verify_url })?;

    let message = Message::new(Email::new(state.config().email.from.email.to_string()))
        .set_template_id(TEMPLATE_ID)
        .add_personalization(personalization);

    state.sendgrid().send(&message).await?;

    info!(user=%user.id, "Email confirmation sent");
    Ok(())
}