Start sending and receiving emails from and to your Crystal application

Azu leverages the Carbon library for writing, sending, and testing emails. Carbon can be configured using the default file generated with a new Azu application in initializers/ In that file, you can add SendGrid keys and change adapters.


Add this to your application's shard.yml:

    github: luckyframework/carbon



First, create a base class for your emails

require "carbon"

# You can setup defaults in this class
abstract class BaseEmail < Carbon::Email
  # For example, set up a default 'from' address
  from"My App Name", "")
  # Use a string if you just need the email address
  from ""

Configure the mailer class

BaseEmail.configure do |settings|
  settings.adapter = true)

Create a class for your email

# Create an email class
class WelcomeEmail < BaseEmail
  def initialize(@name : String, @email_address : String)

  to @email_address
  subject "Welcome, #{@name}!"
  header "My-Custom-Header", "header-value"
  reply_to ""
  # You can also do just `text` or `html` if you don't want both
  templates text, html

Create templates

Templates go in the same folder the email is in:

  • Text email: <folder_email_class_is_in>/templates/<underscored_class_name>/text.ecr

  • HTML email: <folder_email_class_is_in>/templates/<underscored_class_name>/html.ecr

So if your email class is in src/emails/, then your templates would go in src/emails/templates/welcome_email/text|html.ecr.

# in <folder_of_email_class>/templates/welcome_email/text.ecr
# Templates have access to instance variables and methods in the email.
Welcome, <%= @name %>!
# in <folder_of_email_class>/templates/welcome_email/html.ecr
<h1>Welcome, <%= @name %>!</h1>

For more information on what you can do with Embedded Crystal (ECR), see the official Crystal documentation.

Template layouts

Layouts are optional allowing you to specify how each email template looks individually. If you'd like to have the same layout on each, you can create a layout template in <folder_email_class_is_in>/templates/<layout_name>/layout.ecr

In this file, you'll yield the main email body with <%= content %>. Then in your BaseEmail, you can specify the name of the layout.

abstract class BaseEmail < Carbon::Email
  macro inherited
    from default_from
    layout :application_layout
# in src/emails/templates/application_layout/layout.ecr

<h1>Our Email</h1>

<%= content %>


Deliver the email

# Send the email right away!"Kate", "").deliver

# Send the email in the background using `spawn`"Kate", "").deliver_later

Delay email delivery

The built-in delay uses the deliver_later_strategy setting set to Carbon::SpawnStrategy. You can create your own custom delayed strategy that inherits from Carbon::DeliverLaterStrategy and defines a run method that takes a Carbon::Email and a block.

One example might be a job processor:

# Define your new delayed strategy
class SendEmailInJobStrategy < Carbon::DeliverLaterStrategy

  # `` will run `deliver`, but you can call
  # `deliver` yourself on the `email` when you need.
  def run(email : Carbon::Email, &block)

class EmailJob < JobProcessor
  def perform(email : Carbon::Email)

# configure to use your new delayed strategy
BaseEmail.configure do |settings|
  settings.deliver_later_strategy =


Change the adapter

# In spec/ or wherever you configure your code
BaseEmail.configure do
  # This adapter will capture all emails in memory
  settings.adapter =

Reset emails before each spec and include expectations

# In spec/

# This gives you the `be_delivered` expectation
include Carbon::Expectations

Spec.before_each do

Integration testing

# Let's say we have a class that signs the user up and sends the welcome email
# that was described at the beginning of the README
class SignUpUser
  def initialize(@name : String, @email_address : String)

  def run
    sign_user_up @name, email_address: @email_address).deliver

it "sends an email after the user signs up" do "Emily", email_address: "").run

  # Test that this email was sent "Emily", email_address: "").should be_delivered

# or we can just check that some emails were sent
it "sends some emails" do "Emily", email_address: "").run

  Carbon.should have_delivered_emails

Unit testing

Unit testing is simple. Instantiate your email and test the fields you care about.

it "builds a nice welcome email" do
  email = "David", email_address: "")
  # Note that recipients are converted to an array of Carbon::Address
  # So if you use a string value for the `to` field, you'll get an array of
  # Carbon::Address instead. eq ["")]
  email.text_body.should contain "Welcome"
  email.html_body.should contain "Welcome"

Note that unit testing can be superfluous in most cases. Instead, try unit testing just fields that have complex logic. The compiler will catch most other issues.

Last updated