mikel / mail

A Really Ruby Mail Library
MIT License
3.61k stars 936 forks source link

Uniform interface for getting email body? #1600

Open sdhull opened 8 months ago

sdhull commented 8 months ago

Apologies if a better venue for this question would be Stackoverflow or similar—feel free to send me away.

We're on mail 2.8.0.1 & Rails 6.0.6

We have a situation at work where a mailer (actionmailer) has multiple actions which all render the same template, eg:

  def mail_email_template(template, mail_options)
    process_email_template(template)   # sets ivars @liquid_subject and @liquid_body

    mail(mail_options.merge(subject: @liquid_subject)) do |format|
      # Only format html because we use Premailer to take care of text format
      format.html { render "email_template" }   # renders @liquid_body
    end
  end

We also have an after_action that parses the email and adjusts styling based on customer preferences, eg:

  def apply_branding_button_colors
    settings = @user.email_template_branding_settings
    return unless settings
    styler = Email::HtmlStyler.new(mail.decode_body, settings)
    mail.body = styler.html
  end

However, when writing specs to troubleshoot a bug, I found that when the email had an ics attachment, mail.decode_body returned nil, but mail.html_part.to_s returned the email body. So I thought I'd switch to that to be safer, but in a different mailer action being tested (no attachment), mail.html_part.to_s returned an empty string, while mail.body.to_s returned the html body!

(mail in this context is the ActionMailer::Base#mail method which returns an instance of Mail::Message.)

So finally I ended up with email_body = mail.body.to_s.presence || mail.html_part.to_s as a best effort but I wanted to ask:

Am I missing something? Is there a uniform interface to reliably get a string representation of the email body?

timoschilling commented 3 months ago

The reason for that is that a Mail::Message with one part is different to a Mail::Message with multiple part. Having a html part and an attachment means it's multipart. You solution email_body = mail.body.to_s.presence || mail.html_part.to_s is right, maybe just use decoded and not to_s, it's the same but more explicit.

What you can do is that you always add a text part to your mail, so it will always be multipart, no matter an attachment exists or not.