action_mailer-logged_smtp_delivery 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ ### Logged SMTP Delivery
2
+
3
+ A few features:
4
+ 1. Detailed log stream with message id prefix. Example:
5
+ ```
6
+ <4e2b38d772949_b81ac212@localhost> stored at example/log/mails/outbound/2011-07-23/7_13462_2.eml
7
+ <4e2b38d772949_b81ac212@localhost> X-Delivery-Context: [users/1/welcome]
8
+ <4e2b38d772949_b81ac212@localhost> sender: support@support.localhost
9
+ <4e2b38d772949_b81ac212@localhost> destinations: support@system.example.com
10
+ <4e2b38d772949_b81ac212@localhost> done #<Net::SMTP::Response:0x10bbee680 @string="250 2.0.0 Ok: queued as 87BF716D7901\n", @status="250">
11
+ ```
12
+
13
+ 2. Logs an identification header to quickly locate logs for a specific email/entity
14
+ ```ruby
15
+ config.action_mailer.smtp_settings[:log_header] = 'X-Delivery-Context'
16
+
17
+ class UsersMailer < ActionMailer::Base
18
+
19
+ def welcome(user)
20
+ headers['X-Delivery-Context'] = "users/#{user.id}/welcome"
21
+
22
+ # ...
23
+ end
24
+ end
25
+
26
+
27
+ UsersMailer.deliver_welcome(user)
28
+ # ActionMailer::Base.logger ->
29
+ # <4e2b38d772949_b81ac212@localhost> X-Delivery-Context: [users/1/welcome]
30
+ ```
31
+
32
+ 3. Doesn't render BCC recipients
33
+
@@ -0,0 +1,111 @@
1
+ require 'actionmailer'
2
+ require 'active_support/core_ext/class/attribute'
3
+ require 'net/smtp'
4
+
5
+ #TODO: Add mail file logger
6
+
7
+ # A few features:
8
+ # Hides BCC recipients
9
+ # Detailed log stream with message id token
10
+ # Logs an identification header to quickly locate logs for a specific email
11
+ # Can optionally log the raw email
12
+ # TLS support
13
+ module ActionMailer::LoggedSMTPDelivery
14
+ class DeliveryFailure < StandardError
15
+
16
+ delegate :message, :backtrace, :to_s, :to => :original_exception
17
+
18
+ attr_reader :original_exception, :destinations
19
+
20
+ def initialize(original_exception, destinations)
21
+ @original_exception = original_exception
22
+ @destinations = destinations
23
+ end
24
+
25
+ end
26
+
27
+ def self.included(base)
28
+ base.class_attribute :mail_file_logger
29
+ end
30
+
31
+ def perform_delivery_logged_smtp(mail)
32
+ delivery = SMTPDelivery.new(mail, smtp_settings)
33
+ delivery.logger = logger
34
+
35
+ if mail_file_logger
36
+ path = mail_file_logger.log(mail.encoded)
37
+ delivery.log "stored at #{path}"
38
+ end
39
+
40
+ delivery.perform
41
+ end
42
+
43
+ class SMTPDelivery
44
+
45
+ attr_reader :mail, :settings
46
+ attr_accessor :logger
47
+
48
+ def initialize(mail, settings)
49
+ @mail = mail
50
+ @settings = settings
51
+ end
52
+
53
+ def perform
54
+ log_headers
55
+ log "sender: #{sender}"
56
+ log "destinations: #{destinations.inspect}"
57
+
58
+ smtp.start(*settings.values_at(:domain, :user_name, :password, :authentication)) do |session|
59
+ response = session.send_message(message, sender, destinations)
60
+ log "done #{response.inspect}"
61
+ end
62
+ rescue Exception => e
63
+ log "failed #{e.message}"
64
+ raise DeliveryFailure.new(e, destinations)
65
+ end
66
+
67
+ def destinations
68
+ mail.destinations
69
+ end
70
+
71
+ def message
72
+ original_bcc = mail.bcc
73
+ mail.bcc = nil
74
+ mail.encoded
75
+ ensure
76
+ mail.bcc = original_bcc
77
+ end
78
+
79
+ def sender
80
+ mail.from.first
81
+ end
82
+
83
+ def log_header
84
+ settings[:log_header]
85
+ end
86
+
87
+ def smtp
88
+ smtp_adaptor.new(settings[:address], settings[:port]).tap do |smtp|
89
+ smtp.enable_starttls_auto if enable_tls?
90
+ end
91
+ end
92
+
93
+ def log_headers
94
+ log "#{log_header}: [#{mail[log_header]}]" unless log_header.nil?
95
+ end
96
+
97
+ def log(message)
98
+ logger.info("#{mail.message_id} #{message}")
99
+ end
100
+
101
+ def enable_tls?
102
+ settings[:tls] != false
103
+ end
104
+
105
+ def smtp_adaptor
106
+ settings[:adaptor] || Net::SMTP
107
+ end
108
+
109
+ end
110
+
111
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,57 @@
1
+ require 'bundler/setup'
2
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '../lib'))) # lib
3
+ require 'minitest/autorun'
4
+
5
+ class MemoryLogger
6
+
7
+ def log(message)
8
+ messages << message
9
+ end
10
+
11
+ def messages
12
+ @messages ||= []
13
+ end
14
+
15
+ def clear
16
+ messages.clear
17
+ end
18
+
19
+ end
20
+
21
+ class FakeSMTP
22
+
23
+ attr_reader :address, :port, :credentials
24
+
25
+ def self.deliveries
26
+ @deliveries ||= []
27
+ end
28
+
29
+ def initialize(address, port)
30
+ @address = address
31
+ @port = port
32
+ end
33
+
34
+ def start(*credentials)
35
+ @credentials = credentials
36
+ @started = true
37
+
38
+ yield(self)
39
+ end
40
+
41
+ def send_message(message, from, recipients)
42
+ self.class.deliveries << [ message, from, recipients ]
43
+ end
44
+
45
+ def enable_starttls_auto
46
+ @starttls = :auto
47
+ end
48
+
49
+ def starttls_auto?
50
+ @starttls == :auto
51
+ end
52
+
53
+ def inspect
54
+ "#<#{self.class} #{@address}:#{@port} started=#{@started}>"
55
+ end
56
+
57
+ end
@@ -0,0 +1,171 @@
1
+ require_relative 'helper'
2
+ require 'action_mailer/logged_smtp_delivery'
3
+ require 'logger'
4
+
5
+ class LoggedSMTPDeliveryTest < MiniTest::Unit::TestCase
6
+
7
+ class TestMailer < ActionMailer::Base
8
+ include ActionMailer::LoggedSMTPDelivery
9
+
10
+ self.mail_file_logger = MemoryLogger.new
11
+ self.logger = Logger.new(StringIO.new)
12
+ self.delivery_method = :logged_smtp
13
+ self.smtp_settings = { :adaptor => FakeSMTP }
14
+
15
+ def welcome
16
+ recipients 'to@example.com'
17
+ from 'me@example.com'
18
+ body 'hello'
19
+
20
+ # Keep the message simple and consistent between test runs
21
+ headers['mime-version'] = ''
22
+ charset nil
23
+ headers['date'] = ''
24
+ end
25
+
26
+ end
27
+
28
+ describe 'delivering via actionmailer' do
29
+ before do
30
+ TestMailer.mail_file_logger.clear
31
+ FakeSMTP.deliveries.clear
32
+ end
33
+
34
+ it 'logs the mail to a file when the mail file logger is available' do
35
+ TestMailer.deliver_welcome
36
+ assert_equal "From: me@example.com\r\nTo: to@example.com\r\nContent-Type: text/plain\r\n\r\nhello", TestMailer.mail_file_logger.messages.last
37
+ TestMailer.mail_file_logger.messages.clear
38
+
39
+ original_logger = TestMailer.mail_file_logger
40
+ TestMailer.mail_file_logger = nil
41
+ assert TestMailer.deliver_welcome
42
+ TestMailer.mail_file_logger = original_logger
43
+ end
44
+
45
+ it 'delivers the mail' do
46
+ TestMailer.deliver_welcome
47
+ delivery = ["From: me@example.com\r\nTo: to@example.com\r\nContent-Type: text/plain\r\n\r\nhello", "me@example.com", ["to@example.com"]]
48
+
49
+ assert_equal delivery, FakeSMTP.deliveries.last
50
+ end
51
+
52
+ end
53
+
54
+ describe 'SMTP Delivery' do
55
+ before do
56
+ @settings = { :adaptor => FakeSMTP }
57
+ @mail = TMail::Mail.new.tap do |mail|
58
+ mail.message_id = '<12345@example.com>'
59
+ end
60
+ @delivery = ActionMailer::LoggedSMTPDelivery::SMTPDelivery.new(@mail, @settings)
61
+ @log = StringIO.new
62
+ @delivery.logger = Logger.new(@log)
63
+ @delivery.logger.formatter = lambda { |severity, datetime, progname, msg| msg }
64
+ end
65
+
66
+ it 'has the sender via the first from address' do
67
+ @mail.from = [ 'a@example.com', 'b@example.com' ]
68
+ assert_equal 'a@example.com', @delivery.sender
69
+ end
70
+
71
+ it 'has a list of destination addresses' do
72
+ @mail.to = 'to@example.com'
73
+ @mail.cc = 'cc@example.com'
74
+ @mail.bcc = 'bcc@example.com'
75
+
76
+ assert_equal [ 'to@example.com', 'cc@example.com','bcc@example.com' ], @delivery.destinations
77
+ end
78
+
79
+ it 'has an smtp connection' do
80
+ @delivery.settings[:address] = 'example.com'
81
+ @delivery.settings[:port] = 26
82
+
83
+ smtp = @delivery.smtp
84
+ assert_equal 26, smtp.port
85
+ assert_equal 'example.com', smtp.address
86
+ assert_equal true, smtp.starttls_auto?
87
+
88
+ @delivery.settings[:tls] = false
89
+ assert_equal false, @delivery.smtp.starttls_auto?
90
+ end
91
+
92
+ it 'logs with the mail message id' do
93
+ @delivery.log 'hello'
94
+
95
+ assert_equal '<12345@example.com> hello', @log.string
96
+ end
97
+
98
+ it 'logs headers when the log header is provided' do
99
+ @delivery.log_headers
100
+ assert_equal '', @log.string
101
+
102
+ @delivery.settings[:log_header] = 'X-Delivery-Context'
103
+ @delivery.mail['X-Delivery-Context'] = 'hello-33'
104
+ @delivery.log_headers
105
+
106
+ assert_equal '<12345@example.com> X-Delivery-Context: [hello-33]', @log.string
107
+ end
108
+
109
+ it 'sends the mail' do
110
+ @mail.from = 'me@example.com'
111
+ @mail.to = 'to@example.com'
112
+ @mail.cc = 'cc@example.com'
113
+ @mail.bcc = 'bcc@example.com'
114
+ @mail.body = 'hello'
115
+ message = [
116
+ "From: me@example.com\r\nTo: to@example.com\r\nCc: cc@example.com\r\nMessage-Id: <12345@example.com>\r\n\r\nhello",
117
+ "me@example.com",
118
+ ["to@example.com", "cc@example.com", "bcc@example.com"]
119
+ ]
120
+ @delivery.perform
121
+
122
+ assert_equal message, FakeSMTP.deliveries.last
123
+ end
124
+
125
+ it 'does not include BCC addresses in the message' do
126
+ @mail.bcc = 'bcc@example.com'
127
+ assert_equal false, @delivery.message.include?('bcc@example.com')
128
+ end
129
+
130
+ describe "failure" do
131
+ before do
132
+ @delivery.settings[:adaptor] = Class.new do
133
+ def initialize(*args)
134
+ raise StandardError.new("Invalid address")
135
+ end
136
+ end
137
+
138
+ begin
139
+ @mail.from = 'me@example.com'
140
+ @mail.to = 'to@example.com'
141
+ @mail.cc = 'cc@example.com'
142
+ @delivery.perform
143
+ assert false, "Delivery didn't raise an exception"
144
+ rescue StandardError => e
145
+ @failure = e
146
+ end
147
+ end
148
+
149
+ it "provides the original exception" do
150
+ original_exception = @failure.original_exception
151
+ assert_equal StandardError, original_exception.class
152
+ assert_equal "Invalid address", original_exception.message
153
+ end
154
+
155
+ it "provides the destinations that it failed delivery to" do
156
+ assert_equal [ 'to@example.com', 'cc@example.com' ], @failure.destinations
157
+ end
158
+
159
+ it "acts displays the original exception" do
160
+ original_exception = @failure.original_exception
161
+
162
+ assert_equal original_exception.backtrace, @failure.backtrace
163
+ assert_equal original_exception.message, @failure.message
164
+ assert_equal original_exception.to_s, @failure.to_s
165
+ end
166
+
167
+ end
168
+
169
+ end
170
+
171
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: action_mailer-logged_smtp_delivery
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Eric Chapweske
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: actionmailer
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - '='
20
+ - !ruby/object:Gem::Version
21
+ version: 2.3.16
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - '='
28
+ - !ruby/object:Gem::Version
29
+ version: 2.3.16
30
+ - !ruby/object:Gem::Dependency
31
+ name: minitest
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: ActionMailer SMTP delivery strategy with advanced logging and Bcc support
47
+ email:
48
+ executables: []
49
+ extensions: []
50
+ extra_rdoc_files: []
51
+ files:
52
+ - lib/action_mailer/logged_smtp_delivery.rb
53
+ - test/helper.rb
54
+ - test/logged_smtp_delivery_test.rb
55
+ - README.md
56
+ homepage: https://github.com/eac/action_mailer-logged_smtp_delivery
57
+ licenses: []
58
+ post_install_message:
59
+ rdoc_options: []
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubyforge_project:
76
+ rubygems_version: 1.8.21
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: An ActionMailer delivery strategy
80
+ test_files:
81
+ - test/helper.rb
82
+ - test/logged_smtp_delivery_test.rb