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_error writes <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 recipients method (Postmark's errors have one), or else from the mail's to, cc and bcc, and are scrubbed like any other address. They're unknown when the mailer failed before it built the mail.
  • With deliver_later, ActionMailer::MailDeliveryJob passes 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 as deliver_now would (and the ActiveJob integration writes the job's error line). 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

FieldValue
toThe 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.
fromThe first From address, written the same way.
subjectThe subject, scrubbed like any other string (email addresses in it are hashed too).
msg_idThe 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.
mailerThe mailer's class.
mailer_actionThe mailer method that built the mail.
attachmentsHow many attachments the mail has.
account_id, user_idThe id of each instance variable named in actionmailer_id_mapping that the mailer set (by default @account and @user).
error_classerror lines: the error's class.
msgerror lines: the message described above.
backtraceerror 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
end

Each handler writes one error line, then:

  • log_and_ignore_error carries on as if the mail had been sent.
  • log_and_report_error reports the error once, to your error tracker or error_reporting_handler, and carries on.
  • log_and_reraise_error raises the error again: deliver_now raises it, and a deliver_later job fails. It also reports it where config.error_handling_modes.standard_errors reports your app's errors (see Error Handling Configuration): in Report mode, and in ReportProduction mode in production.
In 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_error ignores the error.
  • log_and_report_error reports it to Rails.error (as handled, with the source logstruct.action_mailer) and doesn't raise it.
  • log_and_reraise_error raises 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 (default true).
  • config.integrations.actionmailer_id_mapping (default {account: :account_id, user: :user_id}).
  • config.filters.email_addresses (default true).
  • config.error_handling_modes.standard_errors (default Raise).

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
end

Not 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 delivered line for a failed delivery, even when the handler ignored the error.
  • A deliver_later mail's arguments: its job lines have only the mailer, the mail and the delivery method (see ActiveJob arguments).