ActionMailer
When LogStruct is on, every mail is logged twice: a delivery line just before it's sent and a delivered line once it has been sent. A delivery that fails is an error line. LogStruct gives every mailer a default handler for errors (rescue_from StandardError, with: :log_and_reraise_error) and two more handlers you can name in your own rescue_from calls. Rails' own mail lines, which also log the whole mail at debug level, are turned off.
Lines
delivery
Written before the mail is sent (ActionMailer's before_deliver), at info level. The mail has no Message-ID yet, so there's no msg_id.
{
"src": "mailer",
"evt": "delivery",
"lvl": "info",
"ts": "2026-10-17T03:26:03.062+00:00",
"to": [
"[EMAIL:4a0c62b1a1a5a41c]"
],
"from": "[EMAIL:e26577a2762ca45c]",
"subject": "Welcome, [EMAIL:4a0c62b1a1a5a41c]",
"mailer": "UserMailer",
"mailer_action": "welcome",
"attachments": 0,
"account_id": 3,
"user_id": 12,
"prog": "Rails"
}delivered
Written after the mail was sent (after_deliver), at info level. It isn't written when the delivery fails.
{
"src": "mailer",
"evt": "delivered",
"lvl": "info",
"ts": "2026-10-17T03:26:03.075+00:00",
"to": [
"[EMAIL:4a0c62b1a1a5a41c]"
],
"from": "[EMAIL:e26577a2762ca45c]",
"subject": "Welcome, [EMAIL:4a0c62b1a1a5a41c]",
"msg_id": "[EMAIL:2e7b417e62a0c2f5]",
"mailer": "UserMailer",
"mailer_action": "welcome",
"attachments": 0,
"account_id": 3,
"user_id": 12,
"prog": "Rails"
}error
Written at error level by the mailer's error handler, once per failed delivery. It has the mail's fields, the error's class and backtrace, and a message that says which handler logged it:
{
"src": "mailer",
"evt": "error",
"lvl": "error",
"ts": "2026-10-17T03:26:03.079+00:00",
"to": [
"[EMAIL:4a0c62b1a1a5a41c]"
],
"from": "[EMAIL:e26577a2762ca45c]",
"subject": "Welcome, [EMAIL:4a0c62b1a1a5a41c]",
"msg_id": "[EMAIL:5e2fee655beb1b83]",
"mailer": "RejectedMailer",
"mailer_action": "welcome",
"attachments": 0,
"error_class": "Net::SMTPFatalError",
"msg": "Net::SMTPFatalError: Email delivery failed. Recipients: [EMAIL:4a0c62b1a1a5a41c]. Error message: 550 5.1.1 mailbox unavailable",
"backtrace": [
"app/lib/smtp_client.rb:27:in 'SmtpClient#deliver!'"
],
"account_id": 3,
"user_id": 12,
"prog": "Rails"
}log_and_reraise_errorwrites<ErrorClass>: Email delivery failed. Recipients: <recipients>. Error message: <message>. The other two handlers write<ErrorClass>: Cannot send email to <recipients>. Error message: <message>.- The recipients come from the error's own
recipientsmethod (Postmark's errors have one), or else from the mail'sto,ccandbcc, and are scrubbed like any other address. They'reunknownwhen the mailer failed before it built the mail. - With
deliver_later,ActionMailer::MailDeliveryJobpasses an error that escapes the mailer to the mailer class. An error the mailer already logged is raised again without a second line, so the job fails asdeliver_nowwould (and the ActiveJob integration writes the job'serrorline). An error raised before the mailer ran, such as a record that was deleted before the job ran, is logged by the class with fewer fields:
{
"src": "mailer",
"evt": "error",
"lvl": "error",
"ts": "2026-10-17T03:26:03.084+00:00",
"mailer": "UserMailer",
"error_class": "ActiveJob::DeserializationError",
"msg": "ActiveJob::DeserializationError: Email delivery failed. Error message: Error while trying to deserialize arguments: Couldn't find User with 'id'=12",
"backtrace": [
"app/models/user.rb:1"
],
"prog": "Rails"
}Fields
| Field | Value |
|---|---|
| to | The recipients. With filters.email_addresses on (the default), each is written as [EMAIL:<hash>]; an address has one hash however it's capitalized. With it off, the addresses are written as they are. |
| from | The first From address, written the same way. |
| subject | The subject, scrubbed like any other string (email addresses in it are hashed too). |
| msg_id | The Message-ID (delivered and error lines). It looks like an email address, so with filters.email_addresses on it's written as [EMAIL:<hash>] too. |
| mailer | The mailer's class. |
| mailer_action | The mailer method that built the mail. |
| attachments | How many attachments the mail has. |
| account_id, user_id | The id of each instance variable named in actionmailer_id_mapping that the mailer set (by default @account and @user). |
| error_class | error lines: the error's class. |
| msg | error lines: the message described above. |
| backtrace | error lines: the backtrace, up to max_backtrace_lines (default 20). |
Error handlers
LogStruct adds rescue_from StandardError, with: :log_and_reraise_error to ActionMailer::Base, so every mailer has it. Rails checks a mailer's own rescue_from handlers first, so you can name one of the three handlers for particular errors:
class NotificationMailer < ApplicationMailer
# Log the error, then carry on as if the mail had been sent
rescue_from Net::SMTPFatalError, with: :log_and_ignore_error
# Log the error and report it, then carry on as if the mail had been sent
rescue_from Net::SMTPServerBusy, with: :log_and_report_error
# Other StandardErrors reach LogStruct's default handler, log_and_reraise_error, which logs and raises
# them (reporting them too where error_handling_modes.standard_errors reports errors)
def invoice_ready(user)
mail(to: user.email, subject: "Your invoice is ready", body: "Your invoice is ready.")
end
endEach handler writes one error line, then:
log_and_ignore_errorcarries on as if the mail had been sent.log_and_report_errorreports the error once, to your error tracker orerror_reporting_handler, and carries on.log_and_reraise_errorraises the error again:deliver_nowraises it, and adeliver_laterjob fails. It also reports it whereconfig.error_handling_modes.standard_errorsreports your app's errors (see Error Handling Configuration): inReportmode, and inReportProductionmode in production.
Raise mode, the default for standard errors, LogStruct doesn't report the errors log_and_reraise_error raises. An error tracker that catches raised errors (in the job or the request) may still report them.Where LogStruct is off
In development, the console and Rake tasks (LogStruct is off there by default), or with enable_actionmailer off, LogStruct doesn't add the default handler, but mailers still have the three methods, so a rescue_from that names one doesn't raise NameError. Each writes <ErrorClass>: <message> to the mailer's logger, then:
log_and_ignore_errorignores the error.log_and_report_errorreports it toRails.error(as handled, with the sourcelogstruct.action_mailer) and doesn't raise it.log_and_reraise_errorraises it, without reporting it.
Your own logger calls
Mailers keep their logger. A logger.info call inside a mailer is written as an ordinary line, with src app.
Settings
config.integrations.enable_actionmailer(defaulttrue).config.integrations.actionmailer_id_mapping(default{account: :account_id, user: :user_id}).config.filters.email_addresses(defaulttrue).config.error_handling_modes.standard_errors(defaultRaise).
See Configuration for the other settings.
LogStruct.configure do |config|
# On by default
config.integrations.enable_actionmailer = true
# Instance variables whose IDs are added to each mail line
# (default: {account: :account_id, user: :user_id})
config.integrations.actionmailer_id_mapping = {account: :account_id, user: :user_id, team: :team_id}
# Write email addresses as [EMAIL:<hash>] (default: true)
config.filters.email_addresses = true
endNot logged
- The mail's body, headers other than those above, and attachments' contents.
- Rails' own mail lines ("Delivered mail ..." and the whole mail at debug level).
- A
deliveredline for a failed delivery, even when the handler ignored the error. - A
deliver_latermail's arguments: its job lines have only the mailer, the mail and the delivery method (see ActiveJob arguments).