Skip to content

Say when an emailed error was reopened, and let apps build the subject - #109

Merged
fractaledmind merged 4 commits into
fractaledmind:mainfrom
namolnad:reopened-email-subject
Oct 16, 2026
Merged

fractaledmind merged 4 commits into
fractaledmind:mainfrom
namolnad:reopened-email-subject

Conversation

@namolnad

@namolnad namolnad commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Say when an emailed error was reopened, and let apps build the subject

I know email_subject may be a niche request, and I'd understand if it's more than you want to take on. The "(reopened)" fix at the start stands on its own (see below).

With email_every_occurrence = false, Solid Errors emails an error's first occurrence and the occurrence that reopens a resolved error. Both emails get the same subject (🔥 TimeoutError), so a fix that didn't hold looks the same as a brand-new problem until you open the email. The subject is usually where you decide how urgent an email is.

What changes

By default: a reopened error's email now says so: 🔥 TimeoutError (reopened). Every other subject is unchanged.

email_subject: a new setting. Set it to a proc to build the subject yourself. The proc gets the error, the occurrence, and reason: :created or :reopened when the occurrence opened its error, nil for any other occurrence. email_subject_prefix is still added in front of what it returns.

config.solid_errors.email_subject = ->(error:, occurrence:, reason:) {
  count = error.occurrences.where(id: ..occurrence.id).count
  subject = "#{error.severity_emoji} #{error.exception_class}"

  case reason
  when :reopened then "🔄 REOPENED: #{subject}"
  when :resolved then "✅ RESOLVED: #{error.exception_class}"
  else count.in?([10, 100, 1_000]) ? "#{subject} (#{count.ordinalize} occurrence)" : subject
  end
}

That covers wording, emoji, and with email_every_occurrence = true things like marking the 100th occurrence. Counting up to the occurrence's id, as above, gives the same number however late the mail job runs.

Emails for an app's own events. With the lifecycle notifications from #107, an app can send the error email for events Solid Errors doesn't email about itself. Examples are an error being resolved, or one occurring more often than usual (a rate spike). Whatever reason the app passes to error_occurred reaches the proc, so those emails get their own subjects too:

ActiveSupport::Notifications.subscribe("error_resolved.solid_errors") do |event|
  occurrence = event.payload[:error].occurrences.last
  SolidErrors::ErrorMailer.error_occurred(occurrence, :resolved).deliver_later if occurrence && SolidErrors.send_emails?
end

This is why reason is documented as open-ended. Solid Errors itself only ever passes :created, :reopened or nil.

How

The occurrence already knows whether it opened its error (opens_error). That attribute isn't persisted, though, and deliver_later loads the occurrence again by GlobalID, so the mailer would never see it. The occurrence now passes it to the mailer as an optional second argument, and the mailer hands it to the proc:

ErrorMailer.error_occurred(self, opens_error).deliver_later

error_occurred(occurrence) still works unchanged, so apps that call the mailer directly or override it are unaffected.

If the setting is more than you want

The "(reopened)" fix doesn't depend on the proc. If you'd rather not take on email_subject, I'm happy to cut this back to just the default subject change.

Tests

  • The default subject, with and without :reopened.
  • A custom proc gets the error, the occurrence and the reason, and the prefix is still added to its result.
  • A reason passed by the app, such as :resolved, reaches the proc.
  • Reporting an error again after it was resolved enqueues the email with :reopened. This test fails without the change to Occurrence.

With email_every_occurrence off, the occurrence that reopens a resolved error
is emailed with the same subject as a brand-new error, so a fix that didn't
hold is indistinguishable from a new problem until the email is opened.

The occurrence knows it reopened its error, but the mailer job loads it
afresh, so the reason is passed to the mailer as an optional argument.
The occurrence's reason for being emailed now reaches a proc, along with the
error and the occurrence, so an app can word the subject itself: a different
label for a reopened error, or marking the 100th occurrence when every
occurrence is emailed. The default proc is the existing subject, with
"(reopened)" for an error that occurred again after being resolved.
@namolnad namolnad changed the title Say in the subject when an emailed error was reopened Say when an emailed error was reopened, and let apps build the subject Oct 13, 2026
Whatever reason an app passes to error_occurred already reaches email_subject.
Saying so lets an app build resolved or rate-spike emails on the lifecycle
notifications and still word their subjects in one place.
@namolnad
namolnad marked this pull request as ready for review October 13, 2026 16:47
# Conflicts:
#	CHANGELOG.md
#	test/mailers/solid_errors/error_mailer_test.rb
@fractaledmind
fractaledmind merged commit b9dabc3 into fractaledmind:main Oct 16, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants