### Install Carbon Dependency Source: https://github.com/luckyframework/carbon/blob/main/README.md Add the Carbon library to your Crystal project by including it in your shard.yml file. ```yaml dependencies: carbon: github: luckyframework/carbon ``` -------------------------------- ### Testing Email Delivery with Carbon Expectations Source: https://context7.com/luckyframework/carbon/llms.txt Details how to use Carbon's testing expectations for verifying email delivery within specs. It includes setup, assertions for specific emails, general email counts, and content checks. ```crystal require "spec" require "carbon" # Include expectations module include Carbon::Expectations # Configure for testing BaseEmail.configure do |settings| settings.adapter = Carbon::DevAdapter.new end # Reset before each test Spec.before_each do Carbon::DevAdapter.reset end describe "User signup" do it "sends a welcome email" do # Trigger the action that sends email user = create_user("alice@example.com") # Assert the specific email was delivered WelcomeEmail.new(user.name, user.email).should be_delivered end it "sends some emails during registration" do # Just check that any emails were sent register_user("bob@example.com") Carbon.should have_delivered_emails end it "does not send email to unverified users" do create_unverified_user("test@example.com") Carbon.should_not have_delivered_emails end end describe "Email content" do it "includes the user name in subject" do email = WelcomeEmail.new("Alice", "alice@example.com") email.subject.should eq "Welcome to our app, Alice!" email.to.should eq [Carbon::Address.new("alice@example.com")] email.text_body.should contain "Alice" email.html_body.should contain "Alice" end end ``` -------------------------------- ### Define a Welcome Email Class and Templates (Crystal) Source: https://context7.com/luckyframework/carbon/llms.txt Defines a `WelcomeEmail` class inheriting from `BaseEmail`. It includes initialization with user name and activation URL, specifies recipients, subject, and uses text and HTML templates. Helper methods like `formatted_date` are available within templates. ```crystal class WelcomeEmail < BaseEmail def initialize(@user_name : String, @activation_url : String) end to "user@example.com" subject "Welcome, #{@user_name}!" templates text, html # Helper methods available in templates def formatted_date Time.local.to_s("%B %d, %Y") end end ``` ```ecr Welcome, <%= @user_name %>! Thank you for joining us on <%= formatted_date %>. Activate your account: <%= @activation_url %> ``` ```ecr
Thank you for joining us on <%= formatted_date %>.
``` -------------------------------- ### Configure Testing Adapters and Expectations Source: https://github.com/luckyframework/carbon/blob/main/README.md Sets up the development adapter for capturing emails in memory during tests and includes Carbon expectations for verification. This is typically configured in the spec_helper. ```crystal BaseEmail.configure do settings.adapter = Carbon::DevAdapter.new end include Carbon::Expectations Spec.before_each do Carbon::DevAdapter.reset end ``` -------------------------------- ### Before and After Send Callbacks (Crystal) Source: https://context7.com/luckyframework/carbon/llms.txt Illustrates using `before_send` and `after_send` callbacks to execute code during the email sending process. `before_send` can halt delivery by setting `@deliverable = false`. Callbacks can be defined as blocks or methods. ```crystal class TrackedEmail < BaseEmail def initialize(@recipient : String, @tracking_id : String) end to @recipient subject "Important Update" templates text, html # Block-based callbacks before_send do log_email_attempt(@tracking_id) end after_send do |response| record_delivery_result(@tracking_id, response) end private def log_email_attempt(id) puts "Attempting to send email: #{id}" end private def record_delivery_result(id, response) puts "Email #{id} delivered: #{response}" end end class ConditionalEmail < BaseEmail def initialize(@recipient : String, @can_receive_email : Bool) end to @recipient subject "Newsletter" templates text # Method-based callback before_send :check_subscription after_send :log_result private def check_subscription # Prevent delivery if user unsubscribed @deliverable = false unless @can_receive_email end private def log_result(response) puts "Delivery complete" end end # Usage ConditionalEmail.new("user@example.com", can_receive_email: false).deliver # => Email is NOT sent because deliverable was set to false ``` -------------------------------- ### Apply HTML Layout to Email Templates (Crystal) Source: https://context7.com/luckyframework/carbon/llms.txt Demonstrates how to use a layout template (`app_layout.ecr`) to wrap an email's content. The `NotificationEmail` class specifies `layout app_layout`, which injects its HTML structure around the content from `notification_email/html.ecr`. ```crystal class NotificationEmail < BaseEmail def initialize(@message : String) end to "user@example.com" subject "Notification" templates html layout app_layout # References templates/app_layout/layout.ecr end ``` ```ecr<%= @message %>
``` -------------------------------- ### Implement Email Layouts Source: https://github.com/luckyframework/carbon/blob/main/README.md Define a global layout for your emails using ECR templates and specify the layout name in your BaseEmail class. ```crystal abstract class BaseEmail < Carbon::Email macro inherited from default_from layout :application_layout end end ``` -------------------------------- ### Email Delivery Methods (Crystal) Source: https://context7.com/luckyframework/carbon/llms.txt Shows how to send emails using Carbon. `deliver` sends synchronously, blocking until completion. `deliver_later` sends asynchronously in a background process. The `deliverable?` method and `deliverable` attribute control whether an email is sent. ```crystal # Synchronous delivery - blocks until email is sent email = WelcomeEmail.new("Alice", "alice@example.com") email.deliver # Asynchronous delivery - uses configured deliver_later_strategy email = WelcomeEmail.new("Bob", "bob@example.com") email.deliver_later # Returns immediately, sends in background via spawn # Check if email is deliverable (can be set to false to skip delivery) email = WelcomeEmail.new("Test", "test@example.com") email.deliverable? # => true email.deliverable = false email.deliver # Does nothing - email is not deliverable ``` -------------------------------- ### Perform Unit Testing for Email Fields Source: https://github.com/luckyframework/carbon/blob/main/README.md Shows how to unit test email objects by instantiating them directly and asserting against their properties like recipients and body content. ```crystal it "builds a nice welcome email" do email = WelcomeEmail.new(name: "David", email_address: "david@gmail.com") email.to.should eq [Carbon::Address.new("david@gmail.com")] email.text_body.should contain "Welcome" email.html_body.should contain "Welcome" end ``` -------------------------------- ### Attach IO Memory to Email Source: https://context7.com/luckyframework/carbon/llms.txt Demonstrates how to attach an email directly from an IO::Memory object instead of a file path. This is useful for dynamically generated content. ```crystal class EmailWithIOAttachment < BaseEmail REPORT_DATA = IO::Memory.new("Report content here") def initialize(@recipient : String) end to @recipient subject "Generated Report" templates text # Attach from IO instead of file path attachment({ io: REPORT_DATA, file_name: "report.txt", mime_type: "text/plain" }) end ``` -------------------------------- ### Custom Delayed Email Delivery Strategy Source: https://context7.com/luckyframework/carbon/llms.txt Shows how to implement a custom strategy for delayed email delivery by integrating with a job queue. This allows emails to be processed asynchronously. ```crystal # Custom strategy using a job processor class JobQueueStrategy < Carbon::DeliverLaterStrategy def run(email : Carbon::Email, &block) # Queue the email for later delivery EmailJob.perform_later(email) end end class EmailJob def self.perform_later(email : Carbon::Email) # Add to job queue - implementation depends on your job processor puts "Queued email: #{email.subject}" end def perform(email : Carbon::Email) email.deliver end end # Configure to use custom strategy BaseEmail.configure do |settings| settings.adapter = Carbon::SendGridAdapter.new(api_key: ENV["SENDGRID_API_KEY"]) settings.deliver_later_strategy = JobQueueStrategy.new end # Usage WelcomeEmail.new("user", "user@example.com").deliver_later # => Email is queued via JobQueueStrategy instead of spawned ``` -------------------------------- ### Carbon DevAdapter for Development and Testing Source: https://context7.com/luckyframework/carbon/llms.txt Explains the use of `Carbon::DevAdapter` for capturing sent emails in memory during development and testing. It allows for inspection and verification of email content and delivery. ```crystal # Configure for development BaseEmail.configure do |settings| # print_emails: true will output email details to console settings.adapter = Carbon::DevAdapter.new(print_emails: true) end # Send an email WelcomeEmail.new("Test", "test@example.com").deliver # Access delivered emails Carbon::DevAdapter.delivered_emails # => Array of Carbon::Email # Check if a specific email was delivered email = WelcomeEmail.new("Test", "test@example.com") Carbon::DevAdapter.delivered?(email) # => true or false # Clear all delivered emails (useful between tests) Carbon::DevAdapter.reset Carbon::DevAdapter.delivered_emails # => [] ``` -------------------------------- ### Carbon::Address Class Usage Source: https://context7.com/luckyframework/carbon/llms.txt Illustrates the usage of the `Carbon::Address` class in Crystal for representing email addresses. It shows how to create addresses with and without display names and how to compare them. ```crystal # Create an address with just an email simple_address = Carbon::Address.new("user@example.com") simple_address.address # => "user@example.com" simple_address.name # => nil simple_address.to_s # => "user@example.com" # Create an address with a display name named_address = Carbon::Address.new("John Doe", "john@example.com") named_address.address # => "john@example.com" named_address.name # => "John Doe" named_address.to_s # => "\"John Doe\"