Skip to content

Commit

Permalink
Expose the SMTP envelope From and To addresses and allow them to be o…
Browse files Browse the repository at this point in the history
…verridden.

Envelope From address defaults to return_path || sender || from_addrs.first.
Envelope To address defaults to destinations (to + cc + bcc).

Updates all delivery methods to rely on the SMTP envelope.

References #421 and rails/rails#5985
  • Loading branch information
jeremy committed Jan 28, 2013
1 parent a944f31 commit 4875bc2
Show file tree
Hide file tree
Showing 12 changed files with 252 additions and 128 deletions.
16 changes: 7 additions & 9 deletions lib/mail/check_delivery_params.rb
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
module Mail
module CheckDeliveryParams
def check_delivery_params(mail)
envelope_from = mail.return_path || mail.sender || mail.from_addrs.first
if envelope_from.blank?
raise ArgumentError.new('A sender (Return-Path, Sender or From) required to send a message')
if mail.smtp_envelope_from.blank?
raise ArgumentError.new('An SMTP From address is required to send a message. Set the message smtp_envelope_from, return_path, sender, or from address.')
end

destinations ||= mail.destinations if mail.respond_to?(:destinations) && mail.destinations
if destinations.blank?
raise ArgumentError.new('At least one recipient (To, Cc or Bcc) is required to send a message')
if mail.smtp_envelope_to.blank?
raise ArgumentError.new('An SMTP To address is required to send a message. Set the message smtp_envelope_to, to, cc, or bcc address.')
end

message ||= mail.encoded if mail.respond_to?(:encoded)
message = mail.encoded if mail.respond_to?(:encoded)
if message.blank?
raise ArgumentError.new('A encoded content is required to send a message')
raise ArgumentError.new('An encoded message is required to send an email')
end

[envelope_from, destinations, message]
[mail.smtp_envelope_from, mail.smtp_envelope_to, message]
end
end
end
79 changes: 79 additions & 0 deletions lib/mail/message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ def initialize(*args, &block)
@charset = 'UTF-8'
@defaulted_charset = true

@smtp_envelope_from = nil
@smtp_envelope_to = nil

@perform_deliveries = true
@raise_delivery_errors = true

Expand Down Expand Up @@ -1023,6 +1026,82 @@ def sender=( val )
header[:sender] = val
end

# Returns the SMTP Envelope From value of the mail object, as a single
# string of an address spec.
#
# Defaults to Return-Path, Sender, or the first From address.
#
# Example:
#
# mail.smtp_envelope_from = 'Mikel <[email protected]>'
# mail.smtp_envelope_from #=> '[email protected]'
#
# Also allows you to set the value by passing a value as a parameter
#
# Example:
#
# mail.smtp_envelope_from 'Mikel <[email protected]>'
# mail.smtp_envelope_from #=> '[email protected]'
def smtp_envelope_from( val = nil )
if val
self.smtp_envelope_from = val
else
@smtp_envelope_from || return_path || sender || from_addrs.first
end
end

# Sets the From address on the SMTP Envelope.
#
# Example:
#
# mail.smtp_envelope_from = 'Mikel <[email protected]>'
# mail.smtp_envelope_from #=> '[email protected]'
def smtp_envelope_from=( val )
@smtp_envelope_from = val
end

# Returns the SMTP Envelope To value of the mail object.
#
# Defaults to #destinations: To, Cc, and Bcc addresses.
#
# Example:
#
# mail.smtp_envelope_to = 'Mikel <[email protected]>'
# mail.smtp_envelope_to #=> '[email protected]'
#
# Also allows you to set the value by passing a value as a parameter
#
# Example:
#
# mail.smtp_envelope_to ['Mikel <[email protected]>', 'Lindsaar <[email protected]>']
# mail.smtp_envelope_to #=> ['[email protected]', '[email protected]']
def smtp_envelope_to( val = nil )
if val
self.smtp_envelope_to = val
else
@smtp_envelope_to || destinations
end
end

# Sets the To addresses on the SMTP Envelope.
#
# Example:
#
# mail.smtp_envelope_to = 'Mikel <[email protected]>'
# mail.smtp_envelope_to #=> '[email protected]'
#
# mail.smtp_envelope_to = ['Mikel <[email protected]>', 'Lindsaar <[email protected]>']
# mail.smtp_envelope_to #=> ['[email protected]', '[email protected]']
def smtp_envelope_to=( val )
@smtp_envelope_to =
case val
when Array, NilClass
val
else
[val]
end
end

# Returns the decoded value of the subject field, as a single string.
#
# Example:
Expand Down
18 changes: 8 additions & 10 deletions lib/mail/network/delivery_methods/sendmail.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,26 +41,24 @@ class Sendmail

def initialize(values)
self.settings = { :location => '/usr/sbin/sendmail',
:arguments => '-i -t' }.merge(values)
:arguments => '-i' }.merge(values)
end

attr_accessor :settings

def deliver!(mail)
check_delivery_params(mail)
smtp_from, smtp_to, message = check_delivery_params(mail)

envelope_from = mail.return_path || mail.sender || mail.from_addrs.first
return_path = "-f #{self.class.shellquote(envelope_from)}" if envelope_from
from = "-f #{self.class.shellquote(smtp_from)}"
to = smtp_to.map { |to| self.class.shellquote(to) }.join(' ')

arguments = [settings[:arguments], return_path, '--'].compact.join(" ")

quoted_destinations = mail.destinations.collect { |d| self.class.shellquote(d) }
self.class.call(settings[:location], arguments, quoted_destinations.join(' '), mail)
arguments = "#{settings[:arguments]} #{from} --"
self.class.call(settings[:location], arguments, to, message)
end

def self.call(path, arguments, destinations, mail)
def self.call(path, arguments, destinations, encoded_message)
popen "#{path} #{arguments} #{destinations}" do |io|
io.puts mail.encoded.to_lf
io.puts encoded_message.to_lf
io.flush
end
end
Expand Down
10 changes: 5 additions & 5 deletions lib/mail/network/delivery_methods/smtp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,13 @@ def initialize(values)
:tls => nil
}.merge!(values)
end

attr_accessor :settings

# Send the message via SMTP.
# The from and to attributes are optional. If not set, they are retrieve from the Message.
def deliver!(mail)
envelope_from, destinations, message = check_delivery_params(mail)
smtp_from, smtp_to, message = check_delivery_params(mail)

smtp = Net::SMTP.new(settings[:address], settings[:port])
if settings[:tls] || settings[:ssl]
Expand All @@ -107,10 +107,10 @@ def deliver!(mail)
smtp.enable_starttls_auto(ssl_context)
end
end

response = nil
smtp.start(settings[:domain], settings[:user_name], settings[:password], settings[:authentication]) do |smtp_obj|
response = smtp_obj.sendmail(message, envelope_from, destinations)
response = smtp_obj.sendmail(message, smtp_from, smtp_to)
end

if settings[:return_response]
Expand Down
12 changes: 6 additions & 6 deletions lib/mail/network/delivery_methods/smtp_connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,18 @@ def initialize(values)
self.smtp = values[:connection]
self.settings = values
end

attr_accessor :smtp
attr_accessor :settings

# Send the message via SMTP.
# The from and to attributes are optional. If not set, they are retrieve from the Message.
def deliver!(mail)
envelope_from, destinations, message = check_delivery_params(mail)
response = smtp.sendmail(message, envelope_from, destinations)
smtp_from, smtp_to, message = check_delivery_params(mail)
response = smtp.sendmail(message, smtp_from, smtp_to)

settings[:return_response] ? response : self
settings[:return_response] ? response : self
end

end
end
62 changes: 62 additions & 0 deletions spec/mail/message_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1731,4 +1731,66 @@ def self.delivering_email(mail)

end

describe 'SMTP envelope From' do
it 'should respond' do
Mail::Message.new.should respond_to(:smtp_envelope_from)
end

it 'should default to return_path, sender, or first from address' do
message = Mail::Message.new do
return_path 'return'
sender 'sender'
from 'from'
end
message.smtp_envelope_from.should eq 'return'

message.return_path = nil
message.smtp_envelope_from.should eq 'sender'

message.sender = nil
message.smtp_envelope_from.should eq 'from'
end

it 'can be overridden' do
message = Mail::Message.new { return_path 'return' }

message.smtp_envelope_from = 'envelope_from'
message.smtp_envelope_from.should eq 'envelope_from'

message.smtp_envelope_from = 'declared_from'
message.smtp_envelope_from.should eq 'declared_from'

message.smtp_envelope_from = nil
message.smtp_envelope_from.should eq 'return'
end
end

describe 'SMTP envelope To' do
it 'should respond' do
Mail::Message.new.should respond_to(:smtp_envelope_to)
end

it 'should default to destinations' do
message = Mail::Message.new do
to 'to'
cc 'cc'
bcc 'bcc'
end
message.smtp_envelope_to.should eq message.destinations
end

it 'can be overridden' do
message = Mail::Message.new { to 'to' }

message.smtp_envelope_to = 'envelope_to'
message.smtp_envelope_to.should eq %w(envelope_to)

message.smtp_envelope_to = 'declared_to'
message.smtp_envelope_to.should eq %w(declared_to)

message.smtp_envelope_to = nil
message.smtp_envelope_to.should eq %w(to)
end
end

end
24 changes: 12 additions & 12 deletions spec/mail/network/delivery_methods/exim_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
Mail::Exim.should_receive(:call).with('/usr/sbin/exim',
'-i -t -f "[email protected]" --',
'"[email protected]" "[email protected]"',
mail)
mail.encoded)
mail.deliver!
end

Expand All @@ -54,7 +54,7 @@
Mail::Exim.should_receive(:call).with('/usr/sbin/exim',
'-i -t -f "[email protected]" --',
'"[email protected]"',
mail)
mail.encoded)

mail.deliver

Expand All @@ -77,7 +77,7 @@
Mail::Exim.should_receive(:call).with('/usr/sbin/exim',
'-i -t -f "[email protected]" --',
'"[email protected]"',
mail)
mail.encoded)

mail.deliver
end
Expand All @@ -98,7 +98,7 @@
Mail::Exim.should_receive(:call).with('/usr/sbin/exim',
'-i -t -f "[email protected]" --',
'"[email protected]"',
mail)
mail.encoded)
mail.deliver
end

Expand All @@ -118,7 +118,7 @@
Mail::Exim.should_receive(:call).with('/usr/sbin/exim',
'-i -t -f "\"from+suffix test\"@test.lindsaar.net" --',
'"[email protected]"',
mail)
mail.encoded)
mail.deliver
end

Expand All @@ -135,7 +135,7 @@
Mail::Exim.should_receive(:call).with('/usr/sbin/exim',
'-i -t -f "[email protected]" --',
'"[email protected]"',
mail)
mail.encoded)
mail.deliver
end
end
Expand All @@ -152,9 +152,9 @@
end

Mail::Exim.should_receive(:call).with('/usr/sbin/exim',
'-f "[email protected]" --',
' -f "[email protected]" --',
'"[email protected]" "[email protected]"',
mail)
mail.encoded)
mail.deliver!
end

Expand All @@ -170,9 +170,9 @@
end

Mail::Exim.should_receive(:call).with('/usr/sbin/exim',
"-f \"\\\"foo\\\\\\\"\\;touch /tmp/PWNED\\;\\\\\\\"\\\"@blah.com\" --",
" -f \"\\\"foo\\\\\\\"\\;touch /tmp/PWNED\\;\\\\\\\"\\\"@blah.com\" --",
'"[email protected]"',
mail)
mail.encoded)
mail.deliver!
end

Expand All @@ -186,7 +186,7 @@
subject "Email with no sender"
body "body"
end
end.should raise_error('A sender (Return-Path, Sender or From) required to send a message')
end.should raise_error('An SMTP From address is required to send a message. Set the message smtp_envelope_from, return_path, sender, or from address.')
end

it "should raise an error if no recipient if defined" do
Expand All @@ -199,6 +199,6 @@
subject "Email with no recipient"
body "body"
end
end.should raise_error('At least one recipient (To, Cc or Bcc) is required to send a message')
end.should raise_error('An SMTP To address is required to send a message. Set the message smtp_envelope_to, to, cc, or bcc address.')
end
end
4 changes: 2 additions & 2 deletions spec/mail/network/delivery_methods/file_delivery_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
subject "Email with no sender"
body "body"
end
end.should raise_error('A sender (Return-Path, Sender or From) required to send a message')
end.should raise_error('An SMTP From address is required to send a message. Set the message smtp_envelope_from, return_path, sender, or from address.')
end

it "should raise an error if no recipient if defined" do
Expand All @@ -114,7 +114,7 @@
subject "Email with no recipient"
body "body"
end
end.should raise_error('At least one recipient (To, Cc or Bcc) is required to send a message')
end.should raise_error('An SMTP To address is required to send a message. Set the message smtp_envelope_to, to, cc, or bcc address.')
end

end
Expand Down
Loading

0 comments on commit 4875bc2

Please sign in to comment.