Introduction to emails
Marten lets you define emails in a very declarative way and gives you the ability to fully customize the content of these emails, their properties and associated header values, and obviously how they should be delivered.
Email definition
Emails must be defined as subclasses of the Emailing::Email
abstract class and they usually live in an emails
folder at the root of an application. These classes can define to which email addresses the email is sent (including CC or BCC addresses) and with which templates the body of the email (HTML or plain text) is rendered.
For example, the following snippet defines a simple email that is sent to a specific user's email address:
class WelcomeEmail < Marten::Email
from "[email protected]"
to @user.email
subject "Hello!"
template_name "emails/welcome_email.html"
def initialize(@user : User)
end
end
It is not necessary to systematically specify the from
email address with the #from
macro. Indeed, unless specified, the default "from" email address that is defined in the emailing.from_address
setting is automatically used.
In the above snippet, a WelcomeEmail
email class is defined by inheriting from the Emailing::Email
abstract class. This email is initialized with a hypothetical User
record, and this user's email address is used as the recipient email (through the use of the #to
macro). Other email properties are also defined in the above snippet, such as the "from" email address (#from
macro) and the subject of the email (#subject
macro).
Specifying email properties
Most email properties (eg. from address, recipient addresses, etc) can be specified in two ways:
- through the use of a dedicated macro
- by overriding a corresponding method in the email class
Indeed, it is convenient to define email properties through the use of the dedicated macros: #from
for the sender email, #to
for the recipient addresses, #cc
for the CC addresses, #bcc
for the BCC addresses, #reply_to
for the Reply-To address, and #subject
for the email subject.
That being said, if more complicated logics need to be implemented to generate these email properties, it is perfectly possible to simply override the corresponding method in the considered email class. For example:
class WelcomeEmail < Marten::Email
from "[email protected]"
to @user.email
template_name "emails/welcome_email.html"
def initialize(@user : User)
end
def subject
if @user.referred?
"Glad to see you here!"
else
"Welcome to the app!"
end
end
end
Defining HTML and text bodies
The HTML body (and optionally text body) of the email is rendered using a template whose name can be specified by using the #template_name
class method. By default, unless explicitly specified, it is assumed that the template specified to this method is used for rendering the HTML body of the email. That being said, it is possible to explicitly specify for which content type the template should be used by specifying an optional content_type
argument as follows:
class WelcomeEmail < Marten::Email
to @user.email
subject "Hello!"
template_name "emails/welcome_email.html", content_type: :html
template_name "emails/welcome_email.txt", content_type: :text
def initialize(@user : User)
end
end
Note that it is perfectly valid to specify one template for rendering the HTML body AND another one for rendering the text body (like in the above example).
Note that you can define #html_body
and #text_body
methods if you need to override the logic that allows generating the HTML or text body of your email.
Modifying the template context
All emails have access to a #context
method that returns a template context object. This "global" context object is available for the lifetime of the considered email and can be mutated in order to define which variables are made available to the template runtime when rendering templates in order to generate the HTML/text bodies of your email (which happens when sending the email).
To modify this context object effectively, it's recommended to utilize before_render
callbacks, which are invoked just before rendering a template within your email. For example, this can be achieved as follows:
class WelcomeEmail < Marten::Email
from "[email protected]"
to @user.email
subject "Hello!"
template_name "emails/welcome_email.html"
before_render :prepare_context
def initialize(@user : User)
end
private def prepare_context
context[:user] = @user
end
end
In the above example, the before_render
callback simply assigns a new user
variable to the email template context. Consequently, a corresponding {{ user }}
variable will be available in the template configured for this email (emails/welcome_email.html
in this case).
Defining custom headers
If you need to insert custom headers into your emails, then you can easily do so by defining a #headers
method in your email class. This method must return a hash of string keys and values.
For example:
class WelcomeEmail < Marten::Email
to @user.email
template_name "emails/welcome_email.html"
def initialize(@user : User)
end
def headers
{"X-Foo" => "bar"}
end
end
Sending emails
Emails are sent synchronously through the use of the #deliver
. For example, the WelcomeEmail
email defined in the previous sections could be initialized and delivered by doing:
email = WelcomeEmail.new(user)
email.deliver
When calling #deliver
, the considered email will be delivered by using the currently configured emailing backend.
Emailing backends
Emailing backends define how emails are actually sent when #deliver
gets called. For example, a development backend might simply "collect" the sent emails and print their information to the standard output. Other backends might also integrate with existing email services or interact with an SMTP server to ensure email delivery.
Which backend is used when sending emails is something that is controlled by the emailing.backend
setting. All the available emailing backends are listed in the emailing backend reference.
If necessary, it is also possible to override which emailing backend is used on a per-email basis by leveraging the #backend
class method. For example:
class WelcomeEmail < Marten::Email
from "[email protected]"
to @user.email
subject "Hello!"
template_name "emails/welcome_email.html"
backend Marten::Emailing::Backend::Development.new(print_emails: true)
def initialize(@user : User)
end
end
Callbacks
It is possible to define callbacks in order to bind methods and logics to specific events in the lifecycle of your emails. For example, it is possible to define callbacks that run before or after an email gets delivered.
Please head over to the Email callbacks guide in order to learn more about email callbacks.