16

I would like to alter the Message-ID header that is in the header portion of an email sent from a Ruby on Rails v3 application using ActionMailer.

I am using Sendmail on localhost for mail delivery.

Do I configure this in Sendmail or ActionMailer?

Where do I configure this (if it is ActionMailer): a file in config/ folder or a file in app/mailers/ folder?

james.garriss
  • 12,959
  • 7
  • 83
  • 96
Teddy
  • 18,357
  • 2
  • 30
  • 42

5 Answers5

21

Teddy's answer is good, except that if you actually want each message to have a different ID, you need to make the default a lambda. In the first block of code in his answer, it calculates the message-ID once, at init, and uses the same one for every message.

Here's how I'm doing this in my app:

default "Message-ID" => lambda {"<#{SecureRandom.uuid}@#{Rails.application.config.mailgun_domain}>"}

... with the domain taken from a custom app config variable (and using SecureRandom.uuid, which is a little more straightforward than a SHA-2 based on the timestamp IMO.)

jasoncrawford
  • 2,713
  • 2
  • 21
  • 20
  • 1
    btw - the message-id must be placed between "< >" - otherwise, it doesn't conform to the mail standard. So, the code will be: default "Message-ID" => lambda {"<#{SecureRandom.uuid}@#{Rails.application.config.mailgun_domain}>"} – andrewleung Jan 25 '14 at 04:15
  • 1
    Although this is mostly the right answer, I believe it should be edited to include <> as @andrewleung mentioned. Otherwise, it will be ignored. default "Message-ID" => lambda {"<#{SecureRandom.uuid}@#{Rails.application.config.mailgun_domain}>"} – lsaffie Feb 13 '14 at 16:03
  • in Ruby `1.9.3-p484` I had to add `|v|` to lambda to avoid an exception: `wrong number of arguments (1 for 0)` – sorens May 20 '14 at 16:02
  • Thanks @sorens. My guess is this has less to do with the Ruby version and more to do with the Rails (ActionMailer) version. I think I was on Rails 3.2 when I did this. – jasoncrawford May 24 '14 at 19:00
  • @sorens Or use proc { ... } instead of lambda { |v| ... } ?? Not sure, but there is a suggestion that lambdas started to care about exact argument counts with Ruby 1.9, and at least current ActionMailer is passing in an object. – Justin Maxwell Mar 03 '17 at 19:54
  • I prefer `SecureRandom.hex` , because it is shorter. – Martin T. Jan 25 '21 at 11:28
7

I usually prefer generating the message-id with a UUID. Assuming you have the uuid gem:

headers['Message-ID'] = "<#{ UUID.generate }@example.com>"

Also you should note that according to RFC 2822 the message-id should be placed inside "<" and ">"

Community
  • 1
  • 1
  • I use the uuid gem a lot in other code. That is a great suggestion. It is better to use a UUID vs a SHA256 checksum of the current timestamp. – Teddy May 14 '12 at 13:06
  • It seems since Ruby 1.9.3 you don't need a gem to do this: `require 'securerandom'` followed by `SecureRandom.uuid` – chriscz Oct 26 '21 at 11:46
6

In Rails 4+ (or just Ruby 2.0+) the folowing syntax is working correctly:

default "Message-ID" => ->(v){"<#{Digest::SHA2.hexdigest(Time.now.to_i.to_s)}@yourdomain.com>"}

Tested this with MailCatcher.

Phitherek_
  • 734
  • 1
  • 8
  • 13
3

@jasoncrawford is almost right. The problem is that the mailgun_domain attribute may not be able at development environment, so it is better to access the ActionMailer configs.

default "Message-ID" => lambda {"<#{SecureRandom.uuid}@#{ActionMailer::Base.smtp_settings[:domain]}>"}
John Bachir
  • 22,495
  • 29
  • 154
  • 227
victorhazbun
  • 786
  • 8
  • 16
3

I figured this out. The easiest way to do is to use the default method at the top of the mailer class file.

Example:

require 'digest/sha2'
class UserMailer < ActionMailer::Base
  default "Message-ID"=>"#{Digest::SHA2.hexdigest(Time.now.to_i.to_s)}@yourdomain.com"

  # ... the rest of your mailer class
end

However, I found this difficult to test, so I wrote a private method and used the sent_at time instead of Time.now:

def message_id_in_header(sent_at=Time.now)
  headers["Message-ID"] = "#{Digest::SHA2.hexdigest(sent_at.to_i.to_s)}@yourdomain.com"
end

And I simply called that method before calling the mail method. This made it easy to pass a sent_at parameter from my test and verify a match in email.encoded.

Teddy
  • 18,357
  • 2
  • 30
  • 42
  • 3
    If you actually want each message to have a different ID, you need to make the default a lamba. In the first block of code above, it calculates the message-ID once, at init, and uses the same one for every message. Argh, I'm trying to give some code here and StackOverflow won't let me format it. I'll just add another answer. – jasoncrawford Apr 19 '13 at 03:34
  • 1
    One issue with this approach is that if you send multiple emails per second, all of them will have the same message id – Sairam Jan 14 '21 at 10:44