Handling Postmark Errors
Postmark refuses some mails for good: mail to an inactive recipient (after a hard bounce, a spam complaint or a manual suppression) and requests it can't accept, such as an invalid address. Sending them again won't help. This guide shows how to log those errors without failing the delivery, and how to post an alert to Slack without the recipients' addresses.
What Mailers Do by Default
Where LogStruct is on, it adds rescue_from StandardError, with: :log_and_reraise_error to ActionMailer::Base, so every mailer already has it. A failed delivery is logged as one error line and raised again: deliver_now raises, and a deliver_later job fails. Whether the job runs again is up to your job backend and your retry_on settings. Where LogStruct is off, mailers have no default handler (see below).
The Three Handlers
A mailer can name one of these in its own rescue_from. Each writes one error line, and 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, and adeliver_laterjob fails), and reports it whereconfig.error_handling_modes.standard_errorsreports your app's errors (see Error Handling Configuration): inReportmode, and inReportProductionmode in production.
Raise, LogStruct doesn't report the errors log_and_reraise_error raises, though an error tracker that catches raised errors may report them itself.Where LogStruct is off
Where LogStruct is off (by default in development, consoles, Rake tasks and job runners it doesn't recognize), or with enable_actionmailer off, mailers still have the three methods, so a rescue_from that names one works. Each writes <ErrorClass>: <message> to the mailer's logger, and then:
log_and_ignore_errorignores the error.log_and_report_errorreports it toRails.error(as handled) and doesn't raise it.log_and_reraise_errorraises it, without reporting it.
In a process where LogStruct is off, nothing scrubs that line, and Postmark's messages include the recipient's address.
Ignoring Postmark's Refusals
Name log_and_ignore_error for Postmark's two errors in your ApplicationMailer. Rails checks a class's own handlers before the ones it inherits, so these come before LogStruct's default handler on ActionMailer::Base:
class ApplicationMailer < ActionMailer::Base
# Postmark won't send to these addresses, and sending again won't change that.
# Log the error and carry on as if the mail had been sent.
rescue_from Postmark::InactiveRecipientError,
Postmark::InvalidEmailRequestError,
with: :log_and_ignore_error
# Every other StandardError reaches LogStruct's default handler, log_and_reraise_error
endBoth are subclasses of Postmark::ApiInputError, which the postmark gem raises when its API answers 422. With LogStruct on, a refused mail writes its delivery line, then this error line and no delivered line, in any mode, and a deliver_later job succeeds:
{
"src": "mailer",
"evt": "error",
"lvl": "error",
"ts": "2026-10-17T05:18:13.415+00:00",
"to": [
"[EMAIL:0d68e00d48aadbd7]"
],
"from": "[EMAIL:5e42c76e1a39d521]",
"subject": "Your invoice is ready",
"msg_id": "[EMAIL:0ee4c371a7685a72]",
"mailer": "InvoiceMailer",
"mailer_action": "invoice_ready",
"attachments": 0,
"error_class": "Postmark::InactiveRecipientError",
"msg": "Postmark::InactiveRecipientError: Cannot send email to [EMAIL:0d68e00d48aadbd7]. Error message: You tried to send to recipient(s) that have been marked as inactive. Found inactive addresses: [EMAIL:0d68e00d48aadbd7]. Inactive recipients are ones that have generated a hard bounce, a spam complaint, or a manual suppression.",
"prog": "Rails"
}The line also has the error's backtrace (its first 20 lines by default, config.limits.max_backtrace_lines), left out here. The recipients in the message come from the error's own recipients (an InactiveRecipientError has them) or else from the mail's to, cc and bcc, and are scrubbed like any address. See ActionMailer for the other mail lines.
Slack Alerts
LogStruct doesn't send notifications, but it publishes each of its log entries (the LogStruct::Log structs) as a log.logstruct notification when it logs it, with the entry in payload[:log] and the level in payload[:level]. To post Postmark's errors to Slack, subscribe in an initializer and enqueue a job of your own:
ActiveSupport::Notifications.subscribe("log.logstruct") do |_name, _start, _finish, _id, payload|
log = payload[:log]
next unless log.is_a?(LogStruct::Log::ActionMailer::Error)
next unless [Postmark::InactiveRecipientError, Postmark::InvalidEmailRequestError].include?(log.error_class)
# This is the log entry before LogStruct filtered it: its message, to and subject
# can hold the recipients' addresses. Send only the mailer and the error class.
SlackAlertJob.perform_later(
channel: "#email-alerts",
text: "#{log.mailer_class} couldn't send a mail: #{log.error_class.name}"
)
endmessage, to and subject can hold the recipients' addresses as they are, so send only fields like mailer_class and error_class.- The subscriber runs inside the logger call, after the line is queued to be written. If it raises, the line is still written and the error is handled like your app's other errors (the
standard_errorsmode, which raises it from the logger call by default), so keep it to enqueueing a job. - It runs only where LogStruct handles mail errors: in processes where LogStruct is on, with
enable_actionmaileron (the default). Fordeliver_later, that's the job process. - A refused mail publishes one
LogStruct::Log::ActionMailer::Error, so it sends one alert.
The Error Entry
A subscriber gets a LogStruct::Log::ActionMailer::Error. Its attributes, and their keys in the line:
| Attribute | Key in the line | Value |
|---|---|---|
| error_class | error_class | The exception class (a Class; its name in the line) |
| mailer_class | mailer | The mailer class's name |
| mailer_action | mailer_action | The mailer method |
| message | msg | The error message, with the recipients |
| to, from, subject | to, from, subject | The mail's addresses and subject |
| message_id | msg_id | The mail's Message-ID |
| attachment_count | attachments | How many attachments the mail has |
| backtrace | backtrace | The error's backtrace |
| additional_data | (top level) | IDs from actionmailer_id_mapping, such as account_id |
An error raised before the mailer ran, such as a deliver_later job whose record was deleted before the job ran, is logged by the mailer class, with only mailer_class, error_class, message and backtrace.