From ccdbc5809bf2aec5cf47afb0c8f0ed932f56a8ff Mon Sep 17 00:00:00 2001 From: Jeremy Woertink Date: Fri, 16 Apr 2021 09:57:20 -0700 Subject: [PATCH] Updating docs on emails. Fixes #575 --- .../emails/sending-emails-with-carbon.cr | 162 +++++++++++++++++- 1 file changed, 154 insertions(+), 8 deletions(-) diff --git a/src/actions/guides/emails/sending-emails-with-carbon.cr b/src/actions/guides/emails/sending-emails-with-carbon.cr index ef8dd005..6e86f90c 100644 --- a/src/actions/guides/emails/sending-emails-with-carbon.cr +++ b/src/actions/guides/emails/sending-emails-with-carbon.cr @@ -9,17 +9,23 @@ class Guides::Emails::SendingEmailsWithCarbon < GuideAction <<-MD ## Configuring Email - Lucky leverages the [Carbon](https://github.com/luckyframework/carbon) library for writing, sending, and testing emails. Carbon can be configured using the default file generated with a new Lucky application in `config/email.cr`. In that file you can add SendGrid keys and change adapters. + Lucky leverages the [Carbon](https://github.com/luckyframework/carbon) library for writing, sending, and testing emails. + Carbon can be configured using the default file generated with a new Lucky application in `config/email.cr`. In that file + you can add SendGrid keys and change adapters. ## Adapters - Carbon supports a growing number of adapters thanks to contributions from the community. + Carbon supports a growing number of adapters thanks to contributions from the community. [View supported adapters](https://github.com/luckyframework/carbon#adapters) + + > If you've built an adapter not listed, be sure to let us know! ### Dev Adapter - The `DevAdapter` ships with Carbon by default, and is useful for handling emails in a development or test environment. It can also be leveraged in production to effectively disable emails. + The `DevAdapter` ships with Carbon by default, and is useful for handling emails in a development or test environment. + It can also be leveraged in production to effectively disable emails. - There are two ways to leverage the `DevAdapter`. The first is by telling the adapter to simply capture all Carbon output without printing or displaying the email content, which is the default: + There are two ways to leverage the `DevAdapter`. The first is by telling the adapter to simply capture all Carbon output + without printing or displaying the email content, which is the default: ```crystal # config/email.cr @@ -50,12 +56,152 @@ class Guides::Emails::SendingEmailsWithCarbon < GuideAction end ``` - ## Sending and testing emails + ## Creating Emails + + Emails are setup and configured through Crystal classes that live in your `src/emails/` directory. In that directory, you should already have a + `base_email.cr` file. This is the abstract class all of your email objects will inherit from. Use the `BaseEmail` for any defaults that + should be applied to all of your emails (e.g. `default_from` address, etc...) + + The views (HTML) related to the emails will reside in the `src/emails/templates/{ NAME_OF_EMAIL }/` directory. For example, if your email file + is named `welcome_email.cr`, the templates for this will live in `src/emails/templates/welcome_email/`. + + > You can also check out the `PasswordResetEmail` in the `src/emails/` directory of a newly generated auth project for a live example. + + ### Email templates + + There are two basic templates for emails; HTML, and TEXT. The HTML template will be where you write the raw HTML for your email. The TEXT format + is used as a plain text (no HTML) email for devices and/or email apps that don't support HTML. + + Place the templates inside of each specific email directory they belong to. Then name them `html.ecr`, and `text.ecr`. For example, if your email + file is named `welcome_email.cr`, your templates will be in `src/emails/templates/welcome_email/html.ecr` and `src/emails/templates/welcome_email/text.ecr`. + + The email templates will use [ECR](https://crystal-lang.org/api/1.0.0/ECR.html) for interpolating Crystal code. All instance variables/methods defined in + your email class will be available within your template. - See the README at https://github.com/luckyframework/carbon + ```html + +

Welcome, <%= @user.name %>!

+

...

+

Secret token <%= @token %>.

+

Thanks, <%= email_signature %>

+ ``` + + ### Email class + + In the `BaseEmail`, you can set defaults that will apply to all of your emails. This includes setting special email headers, `from` address, + or maybe helper methods you need to use in your email templates. + + ```crystal + # src/emails/base_email.cr + abstract class BaseEmail < Carbon::Email + macro inherited + from default_from + header "Return-Path", "hello@myapp.io" + header "Message-ID", default_message_id + end + + def default_from + Carbon::Address.new("hello@myapp.io") + end + + def default_message_id + digest = OpenSSL::Digest.new("SHA256") + digest.update(Time.utc.to_unix.to_s) + message_id = digest.final.hexstring + + "<\#{message_id}@myapp.io>" + end + + def email_signature : String + "The MyApp Crew" + end + end + ``` + + ```crystal + # src/emails/welcome_email.cr + class WelcomeEmail < BaseEmail + + # Define your own initializer with the + # references it needs + def initializer(@user : User) + encryptor = Lucky::MessageEncryptor.new(secret: Lucky::Server.settings.secret_key_base) + + # Instance variables defined are available in your templates + @token = encryptor.encrypt_and_sign("\#{@user.id}:\#{24.hours.from_now.to_unix_ms}") + end + + to @user + subject "Welcome to MyApp.io!" + templates html, text + end + ``` + + ## Sending Emails + + There's two strategies to sending emails; deliver now, or deliver later. + + ### Deliver email now + + Once your email class is defined, you can call the `deliver` method to send now. + + ```crystal + WelcomeEmail.new(current_user).deliver + ``` + + ### Deliver email later + + If you need to delay sending the email, call the `deliver_later` method to send later. + + ```crystal + WelcomeEmail.new(current_user).deliver_later + ``` + + [read more](https://github.com/luckyframework/carbon#delay-email-delivery) on the `deliver_later` strategy. + + ## Testing Emails + + Carbon comes with a few methods you can use in your specs to ensure emails are being sent. [read more](https://github.com/luckyframework/carbon#testing) + + To configure testing your emails, you'll need to add `include Carbon::Expectations` in to `spec/spec_helper.cr`. + Then to make sure that emails are cleared between specs, you need to add `spec/setup/reset_emails.cr` with + + ```crystal + Spec.before_each do + Carbon::DevAdapter.reset + end + ``` + + ### be_delivered expectation + + The `be_delivered` expectation is used to assert a specific email was delivered. + + > This only checks that the `deliver` method was called. It does not account for API hanlding in other adapters. + + ```crystal + it "delivers the email" do + user = UserFactory.create &.email("emily@gmail.com") + WelcomeEmail.new(user).deliver_now + + # Test that this email was sent + WelcomeEmail.new(user).should be_delivered + end + ``` + + ### have_delivered_emails expectation + + The `have_delivered_emails` is a bit more generic, and asserts that Carbon sent *any* email. + + ```crystal + it "delivers the email" do + user = UserFactory.create &.email("emily@gmail.com") + WelcomeEmail.new(user).deliver_now + + # Test that any email was sent + Carbon.should have_delivered_emails + end + ``` - You can also check out the `PasswordResetEmail` in the `src/emails` directory - of a newly generated project. MD end end