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/carbon.cr. In that file, you can add SendGrid keys and change adapters.
require"carbon"# You can setup defaults in this classabstractclassBaseEmail < Carbon::Email # For example, set up a default 'from' address from Carbon::Address.new("My App Name","support@myapp.com") # Use a string if you just need the email address from "support@myapp.com"end
Configure the mailer class
BaseEmail.configure do |settings| settings.adapter =Carbon::DevAdapter.new(print_emails: true)end
Create a class for your email
# Create an email classclassWelcomeEmail < BaseEmaildefinitialize(@name :String, @email_address :String)end to @email_address subject "Welcome, #{@name}!" header "My-Custom-Header","header-value" reply_to "no-reply@noreply.com" # You can also do just `text` or `html` if you don't want both templates text, htmlend
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/welcome_email.cr, 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>
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.
abstractclassBaseEmail < Carbon::Emailmacro inherited from default_from layout :application_layoutendend
# in src/emails/templates/application_layout/layout.ecr<h1>Our Email</h1><%= content %><div>footer</div>
Deliver the email
# Send the email right away!WelcomeEmail.new("Kate","kate@example.com").deliver# Send the email in the background using `spawn`WelcomeEmail.new("Kate","kate@example.com").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 strategyclassSendEmailInJobStrategy < Carbon::DeliverLaterStrategy # `block.call` will run `deliver`, but you can call # `deliver` yourself on the `email` when you need.defrun(email:Carbon::Email, &block)EmailJob.perform_later(email)endendclassEmailJob < JobProcessordefperform(email:Carbon::Email) email.deliverendend# configure to use your new delayed strategyBaseEmail.configure do |settings| settings.deliver_later_strategy =SendEmailInJobStrategy.newend
Testing
Change the adapter
# In spec/spec_helper.cr or wherever you configure your codeBaseEmail.configure do # This adapter will capture all emails in memory settings.adapter =Carbon::DevAdapter.newend
Reset emails before each spec and include expectations
# In spec/spec_helper.cr# This gives you the `be_delivered` expectationincludeCarbon::ExpectationsSpec.before_each doCarbon::DevAdapter.resetend
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 READMEclassSignUpUserdefinitialize(@name :String, @email_address :String)enddefrun sign_user_upWelcomeEmail.new(name: @name, email_address: @email_address).deliverendendit"sends an email after the user signs up"doSignUpUser.new(name: "Emily", email_address: "em@gmail.com").run # Test that this email was sentWelcomeEmail.new(name: "Emily", email_address: "em@gmail.com").should be_deliveredend# or we can just check that some emails were sentit"sends some emails"doSignUpUser.new(name: "Emily", email_address: "em@gmail.com").runCarbon.should have_delivered_emailsend
Unit testing
Unit testing is simple. Instantiate your email and test the fields you care about.
it"builds a nice welcome email"do email =WelcomeEmail.new(name: "David", email_address: "david@gmail.com") # 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. email.to.should eq [Carbon::Address.new("david@gmail.com")] email.text_body.should contain "Welcome" email.html_body.should contain "Welcome"end
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.