dvdplm-ar_mailer 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/History.txt ADDED
@@ -0,0 +1,104 @@
1
+ = 2.0.2
2
+
3
+ * Bugs fixed
4
+ * Email class reloading issue in development mode causing AR email class defaults to be lost when cached
5
+
6
+ = 2.0.1
7
+
8
+ * Added option to use smtp setting of :tls => false to disable TLS auto start in Ruby 1.8.7+
9
+ * Removed some cruft which can be handled by ActiveSupport
10
+
11
+ = 2.0.0
12
+
13
+ * Removed need to use ARMailer subclass. Just set the delivery method and you are ready to go. Backwards compatible with a deprecation notice if you subclass old ARMailer class.
14
+ * Only include SMTP TLS patch if Ruby version < 1.8.7 as it has an alternative. Changes based on Calvin Yu's [cyu] fork.
15
+ * Renamed default migration name to the modern Rails default
16
+ * Only authenticate if emails waiting to be sent
17
+ * Added --version switch to ar_sendmail binary
18
+ * Created a lighthouse account for this project (adzap fork only). See README.
19
+
20
+ = 1.4.4
21
+
22
+ * Exit init.d script with message if no mailers defined.
23
+
24
+ = 1.4.3
25
+
26
+ * Bugs fixed
27
+ * Replaced mistaken call to log when removing pid file artifact for
28
+ non-running daemon
29
+
30
+ = 1.4.2
31
+
32
+ * New Features
33
+ * Added Ruby based linux init.d script for handling daemon startup using yaml
34
+ config file. See files share/linux/ar_sendmail and ar_sendmail.conf
35
+ * Bugs fixed
36
+ * Proper handling for relative and absolute paths for the pid file
37
+ * Removed hoe dependency since we need the explicit gemspec file for github and
38
+ not deploying to RubyForge its not as useful.
39
+ * Moved old BSD rc.d script to share/bsd folder
40
+ * Updated README with github gem install, docs and init script info
41
+
42
+ = 1.4.1
43
+
44
+ * Bugs fixed
45
+ * Daemon failed on startup fixed with expanding full path of pid file
46
+
47
+ = 1.4.0
48
+
49
+ * Forked gem and published on GitHub (gem sources -a http://gems.github.com)
50
+ * New Features
51
+ * Added pid file creation on daemonize with command line option to specify pid filename [Dylan Egan]
52
+
53
+ = 1.3.1
54
+
55
+ * Fix bug #12530, gmail causes SSL errors. Submitted by Kyle Maxwell
56
+ and Alex Ostleitner.
57
+ * Try ActionMailer::Base::server_settings then ::smtp_settings. Fixes
58
+ bug #12516. Submitted by Alex Ostleitner.
59
+
60
+ = 1.3.0
61
+
62
+ * New Features
63
+ * Added automatic mail queue cleanup.
64
+ * MAY CAUSE LOSS OF DATA. If you haven't run ar_sendmail within
65
+ the expiry time, set it to 0.
66
+ * Bugs fixed
67
+ * Authentication errors are now handled by retrying once.
68
+
69
+ = 1.2.0
70
+
71
+ * Bugs fixed
72
+ * Handle SMTPServerBusy by backing off @delay seconds then re-queueing
73
+ * Allow email delivery class to be set in ARMailer.
74
+ * ar_sendmail --mailq works with --table-name now.
75
+ * Miscellaneous Updates
76
+ * Added documentation to require 'action_mailer/ar_mailer' in
77
+ instructions.
78
+ * Moved to ZSS p4 repository
79
+ * Supports TLS now. Requested by Dave Thomas. smtp_tls.rb from Kyle
80
+ Maxwell & etc.
81
+
82
+ = 1.1.0
83
+
84
+ * Features
85
+ * Added --chdir to set rails directory
86
+ * Added --environment to set RAILS_ENV
87
+ * Exits cleanly on TERM or INT signals
88
+ * Added FreeBSD rc.d script
89
+ * Exceptions during SMTP sending are now logged
90
+ * No longer waits if sending email took too long
91
+ * Bugs fixed
92
+ * Fixed last send attempt in --mailq
93
+ * Better SMTP error handling
94
+ * Messages are removed from the queue on 5xx errors
95
+ * Added Net::SMTP.reset to avoid needing to recreate the connection
96
+
97
+ = 1.0.1
98
+
99
+ * Bugs fixed
100
+ * From and to of email destination were swapped
101
+
102
+ = 1.0.0
103
+
104
+ * Birthday
data/LICENSE.txt ADDED
@@ -0,0 +1,28 @@
1
+ Original code copyright 2006, 2007, Eric Hodel, The Robot Co-op. All
2
+ rights reserved. Some code under other license, see individual files
3
+ for details.
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions
7
+ are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright
10
+ notice, this list of conditions and the following disclaimer.
11
+ 2. Redistributions in binary form must reproduce the above copyright
12
+ notice, this list of conditions and the following disclaimer in the
13
+ documentation and/or other materials provided with the distribution.
14
+ 3. Neither the names of the authors nor the names of their contributors
15
+ may be used to endorse or promote products derived from this software
16
+ without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
19
+ OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
22
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
23
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
24
+ OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
25
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
27
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/Manifest.txt ADDED
@@ -0,0 +1,15 @@
1
+ History.txt
2
+ LICENSE.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ bin/ar_sendmail
7
+ lib/action_mailer/ar_mailer.rb
8
+ lib/action_mailer/ar_sendmail.rb
9
+ lib/smtp_tls.rb
10
+ share/bsd/ar_sendmail
11
+ share/linux/ar_sendmail
12
+ share/linux/ar_sendmail.conf
13
+ test/action_mailer.rb
14
+ test/test_armailer.rb
15
+ test/test_arsendmail.rb
data/README.rdoc ADDED
@@ -0,0 +1,139 @@
1
+ = reason for fork
2
+ The purpose of this fork is to keep an archive of sent/unsent emails.
3
+
4
+ The Email class (or whatever you prefer to call it) in this for has different attributes.
5
+
6
+ = ar_mailer
7
+
8
+ A two-phase delivery agent for ActionMailer
9
+
10
+ Rubyforge Project:
11
+
12
+ http://rubyforge.org/projects/seattlerb
13
+
14
+ Documentation:
15
+
16
+ http://seattlerb.org/ar_mailer
17
+
18
+ and for forked additions
19
+
20
+ http://github.com/adzap/ar_mailer/wikis
21
+
22
+ Bugs:
23
+
24
+ http://adzap.lighthouseapp.com/projects/26997-ar_mailer
25
+
26
+ == About
27
+
28
+ Even delivering email to the local machine may take too long when you have to
29
+ send hundreds of messages. ar_mailer allows you to store messages into the
30
+ database for later delivery by a separate process, ar_sendmail.
31
+
32
+ == Installing ar_mailer (forked)
33
+
34
+ Before installing you will need to make sure the original gem is uninstalled as they can't coexist:
35
+
36
+ $ sudo gem uninstall ar_mailer
37
+
38
+ Install the gem from GitHub gems server:
39
+
40
+ First, if you haven't already:
41
+
42
+ $ sudo gem sources -a http://gems.github.com
43
+
44
+ Then
45
+
46
+ $ sudo gem install dvdplm-ar_mailer
47
+
48
+ For Rails >= 2.1, in your environment.rb:
49
+
50
+ config.gem "dvdplm-ar_mailer", :lib => 'action_mailer/ar_mailer', :source => 'http://gems.github.com'
51
+
52
+ For Rails 2.0, in an initializer file:
53
+
54
+ require 'action_mailer/ar_mailer'
55
+
56
+ == Usage
57
+
58
+ Go to your Rails project:
59
+
60
+ $ cd your_rails_project
61
+
62
+ Create a new migration:
63
+
64
+ $ ar_sendmail --create-migration
65
+
66
+ You'll need to redirect this into a file. If you want a different name
67
+ provide the --table-name option.
68
+
69
+ Create a new model:
70
+
71
+ $ ar_sendmail --create-model
72
+
73
+ You'll need to redirect this into a file. If you want a different name
74
+ provide the --table-name option.
75
+
76
+ You'll need to be sure to set the From address for your emails. Something
77
+ like:
78
+
79
+ def list_send(recipient)
80
+ from 'no_reply@example.com'
81
+ # ...
82
+
83
+ Edit config/environments/production.rb and set the delivery method:
84
+
85
+ config.action_mailer.delivery_method = :activerecord
86
+
87
+ Or if you need to, you can set each mailer class delivery method individually:
88
+
89
+ class MyMailer < ActionMailer::Base
90
+ self.delivery_method = :activerecord
91
+ end
92
+
93
+ This can be useful when using plugins like ExceptionNotification. Where it
94
+ might be foolish to tie the sending of the email alert to the database when the
95
+ database might be causing the exception being raised. In this instance you could
96
+ override ExceptionNofitier delivery method to be smtp or set the other
97
+ mailer classes to use ARMailer explicitly.
98
+
99
+ Then to run it:
100
+
101
+ $ ar_sendmail
102
+
103
+ You can also run it from cron with -o, or as a daemon with -d.
104
+
105
+ See <tt>ar_sendmail -h</tt> for full details.
106
+
107
+ === Alternate Mail Storage
108
+
109
+ If you want to set the ActiveRecord model that emails will be stored in,
110
+ see ActionMailer::Base.email_class=
111
+
112
+ === A Word on TLS
113
+
114
+ If you are using Ruby >= 1.8.7, TLS will be enabled automatically if your
115
+ SMTP server supports it. If you do not want it to automatically enabled then
116
+ set the :tls option to false in your smtp_settings.
117
+
118
+ If you are on Ruby <= 1.8.6, then the TLS patch included in this plugin will
119
+ be loaded, so you don't need another TLS plugin to add the capability. This
120
+ patch allows you to explicit set if the server supports TLS by setting the
121
+ :tls option to true in your smtp_settings.
122
+
123
+ === Help
124
+
125
+ See ar_sendmail -h for options to ar_sendmail.
126
+
127
+ NOTE: You may need to delete an smtp_tls.rb file if you have one lying
128
+ around. ar_mailer supplies it own.
129
+
130
+ == Run as a service (init.d/rc.d scripts)
131
+
132
+ For Linux both script and demo config files are in share/linux.
133
+ See ar_sendmail.conf for setting up your config. Copy the ar_sendmail file
134
+ to /etc/init.d/ and make it executable. Then for Debian based distros run
135
+ 'sudo update-rc.d ar_sendmail defaults' and it should work. Make sure you have
136
+ the config file /etc/ar_sendmail.conf in place before starting.
137
+
138
+ For FreeBSD or NetBSD script is share/bsd/ar_sendmail. This is old and does not
139
+ support the config file unless someone wants to submit a patch.
data/Rakefile ADDED
@@ -0,0 +1,58 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+
6
+ $:.unshift(File.expand_path(File.dirname(__FILE__) + '/lib'))
7
+
8
+ require './lib/action_mailer/ar_sendmail'
9
+
10
+ ar_mailer_gemspec = Gem::Specification.new do |s|
11
+ s.name = %q{ar_mailer}
12
+ s.version = ActionMailer::ARSendmail::VERSION
13
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
14
+ s.authors = ["Eric Hodel", "Adam Meehan"]
15
+ s.default_executable = %q{ar_sendmail}
16
+ s.description = %q{Even delivering email to the local machine may take too long when you have to send hundreds of messages. ar_mailer allows you to store messages into the database for later delivery by a separate process, ar_sendmail.}
17
+ s.email = %q{adam.meehan@gmail.com}
18
+ s.executables = ["ar_sendmail"]
19
+ s.extra_rdoc_files = ["History.txt", "LICENSE.txt", "Manifest.txt", "README.rdoc"]
20
+ s.files = ["History.txt", "LICENSE.txt", "Manifest.txt", "README.rdoc", "Rakefile", "bin/ar_sendmail", "lib/action_mailer/ar_mailer.rb", "lib/action_mailer/ar_sendmail.rb", "lib/smtp_tls.rb", "share/bsd/ar_sendmail", "share/linux/ar_sendmail", "share/linux/ar_sendmail.conf", "test/action_mailer.rb", "test/test_armailer.rb", "test/test_arsendmail.rb"]
21
+ s.has_rdoc = true
22
+ s.homepage = %q{http://github.com/adzap/ar_mailer}
23
+ s.rdoc_options = ["--main", "README.rdoc"]
24
+ s.require_paths = ["lib"]
25
+ s.rubyforge_project = %q{seattlerb}
26
+ s.summary = %q{A two-phase delivery agent for ActionMailer}
27
+ s.test_files = ["test/test_armailer.rb", "test/test_arsendmail.rb"]
28
+ end
29
+
30
+ Rake::GemPackageTask.new(ar_mailer_gemspec) do |pkg|
31
+ pkg.gem_spec = ar_mailer_gemspec
32
+ end
33
+
34
+ namespace :gem do
35
+ namespace :spec do
36
+ desc "Update ar_mailer.gemspec"
37
+ task :generate do
38
+ File.open("ar_mailer.gemspec", "w") do |f|
39
+ f.puts(ar_mailer_gemspec.to_ruby)
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ desc "Build packages and install"
46
+ task :install => :package do
47
+ sh %{sudo gem install --local pkg/ar_mailer-#{ActionMailer::ARSendmail::VERSION}}
48
+ end
49
+
50
+ desc 'Default: run unit tests.'
51
+ task :default => :test
52
+
53
+ desc 'Test the ar_mailer gem.'
54
+ Rake::TestTask.new(:test) do |t|
55
+ t.libs << 'lib' << 'test'
56
+ t.pattern = 'test/**/test_*.rb'
57
+ t.verbose = true
58
+ end
data/bin/ar_sendmail ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'action_mailer/ar_sendmail'
4
+
5
+ ActionMailer::ARSendmail.run
6
+
@@ -0,0 +1,41 @@
1
+ require 'action_mailer'
2
+
3
+ ##
4
+ # Adds sending email through an ActiveRecord table as a delivery method for
5
+ # ActionMailer.
6
+ #
7
+
8
+ class ActionMailer::ARMailer < ActionMailer::Base
9
+
10
+ def self.inherited(sub)
11
+ logger.warn('The ActionMailer::ARMailer class has been deprecated. Will be removed in version 2.1. Just use ActionMailer::Base.')
12
+ end
13
+
14
+ end
15
+
16
+ class ActionMailer::Base
17
+
18
+ ##
19
+ # Set the email class for deliveries. Handle class reloading issues which prevents caching the email class.
20
+ #
21
+ @@email_class_name = 'Email'
22
+
23
+ def self.email_class=(klass)
24
+ @@email_class_name = klass.to_s
25
+ end
26
+
27
+ def self.email_class
28
+ @@email_class_name.constantize
29
+ end
30
+
31
+ ##
32
+ # Adds +mail+ to the Email table. Only the first From address for +mail+ is
33
+ # used.
34
+
35
+ def perform_delivery_activerecord(mail)
36
+ mail.destinations.each do |destination|
37
+ self.class.email_class.create :mail => mail.encoded, :to => destination, :from => mail.from.first
38
+ end
39
+ end
40
+
41
+ end
@@ -0,0 +1,577 @@
1
+ require 'optparse'
2
+ require 'net/smtp'
3
+ require 'smtp_tls' unless Net::SMTP.instance_methods.include?("enable_starttls_auto")
4
+ require 'rubygems'
5
+
6
+ ##
7
+ # Hack in RSET
8
+
9
+ module Net # :nodoc:
10
+ class SMTP # :nodoc:
11
+
12
+ unless instance_methods.include? 'reset' then
13
+ ##
14
+ # Resets the SMTP connection.
15
+
16
+ def reset
17
+ getok 'RSET'
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+
24
+ module ActionMailer; end # :nodoc:
25
+
26
+ ##
27
+ # ActionMailer::ARSendmail delivers email from the email table to the
28
+ # SMTP server configured in your application's config/environment.rb.
29
+ # ar_sendmail does not work with sendmail delivery.
30
+ #
31
+ # ar_mailer can deliver to SMTP with TLS using smtp_tls.rb borrowed from Kyle
32
+ # Maxwell's action_mailer_optional_tls plugin. Simply set the :tls option in
33
+ # ActionMailer::Base's smtp_settings to true to enable TLS.
34
+ #
35
+ # See ar_sendmail -h for the full list of supported options.
36
+ #
37
+ # The interesting options are:
38
+ # * --daemon
39
+ # * --mailq
40
+ # * --create-migration
41
+ # * --create-model
42
+ # * --table-name
43
+
44
+ class ActionMailer::ARSendmail
45
+
46
+ ##
47
+ # The version of ActionMailer::ARSendmail you are running.
48
+
49
+ VERSION = '2.0.3'
50
+
51
+ ##
52
+ # Maximum number of times authentication will be consecutively retried
53
+
54
+ MAX_AUTH_FAILURES = 2
55
+
56
+ ##
57
+ # Email delivery attempts per run
58
+
59
+ attr_accessor :batch_size
60
+
61
+ ##
62
+ # Seconds to delay between runs
63
+
64
+ attr_accessor :delay
65
+
66
+ ##
67
+ # Maximum age of emails in seconds before they are removed from the queue.
68
+
69
+ attr_accessor :max_age
70
+
71
+ ##
72
+ # Be verbose
73
+
74
+ attr_accessor :verbose
75
+
76
+ ##
77
+ # ActiveRecord class that holds emails
78
+
79
+ attr_reader :email_class
80
+
81
+ ##
82
+ # True if only one delivery attempt will be made per call to run
83
+
84
+ attr_reader :once
85
+
86
+ ##
87
+ # Times authentication has failed
88
+
89
+ attr_accessor :failed_auth_count
90
+
91
+ @@pid_file = nil
92
+
93
+ def self.remove_pid_file
94
+ if @@pid_file
95
+ require 'shell'
96
+ sh = Shell.new
97
+ sh.rm @@pid_file
98
+ end
99
+ end
100
+
101
+ ##
102
+ # Creates a new migration using +table_name+ and prints it on stdout.
103
+
104
+ def self.create_migration(table_name)
105
+ # TODO: add indexes where appropriate! (dvd, 11-05-2009)
106
+ require 'active_support'
107
+ puts <<-EOF
108
+ class Create#{table_name.classify} < ActiveRecord::Migration
109
+ def self.up
110
+ create_table :#{table_name.tableize} do |t|
111
+ t.column :to, :string
112
+ t.column :from, :string
113
+ t.column :mail, :text
114
+ t.column :last_send_attempt, :integer, :default => 0
115
+ t.column :last_error, :text
116
+ t.column :attempts, :integer
117
+ t.column :failed, :boolean, :default => false
118
+ t.column :created_at, :datetime
119
+ t.column :updated_at, :datetime
120
+ t.column :sent_at, :datetime
121
+ end
122
+ end
123
+
124
+ def self.down
125
+ drop_table :#{table_name.tableize}
126
+ end
127
+ end
128
+ EOF
129
+ end
130
+
131
+ ##
132
+ # Creates a new model using +table_name+ and prints it on stdout.
133
+
134
+ def self.create_model(table_name)
135
+ require 'active_support'
136
+ puts <<-EOF
137
+ class #{table_name.classify} < ActiveRecord::Base
138
+ def sent?
139
+ not failed? and not sent_at.nil?
140
+ end
141
+ end
142
+ EOF
143
+ end
144
+
145
+ ##
146
+ # Prints a list of unsent emails and the last delivery attempt, if any.
147
+ #
148
+ # If ActiveRecord::Timestamp is not being used the arrival time will not be
149
+ # known. See http://api.rubyonrails.org/classes/ActiveRecord/Timestamp.html
150
+ # to learn how to enable ActiveRecord::Timestamp.
151
+
152
+ def self.mailq(table_name)
153
+ klass = table_name.split('::').inject(Object) { |k,n| k.const_get n }
154
+ emails = klass.find :all, :conditions => {:sent_at => nil, :failed => false}
155
+
156
+ if emails.empty? then
157
+ puts "Mail queue is empty"
158
+ return
159
+ end
160
+
161
+ total_size = 0
162
+
163
+ puts "-Queue ID- --Size-- ----Arrival Time---- -----Sent At------ -Attempts- -Sender/Recipient--------------------------------------"
164
+ emails.each do |email|
165
+ size = email.mail.length
166
+ total_size += size
167
+
168
+ create_timestamp = email.created_on rescue
169
+ email.created_at rescue
170
+ Time.at(email.created_date) rescue # for Robot Co-op
171
+ nil
172
+
173
+ created = if create_timestamp.nil? then
174
+ ' Unknown'
175
+ else
176
+ create_timestamp.strftime '%a %b %d %H:%M:%S'
177
+ end
178
+
179
+ puts "%10d %8d %s %s %10d %s -> %s" % [email.id, size, created, email.sent_at || ' '*19, email.attempts, email.from, email.to]
180
+ if email.last_send_attempt > 0 then
181
+ puts "Last send attempt: #{Time.at email.last_send_attempt}"
182
+ puts "Attempt no: #{email.attempts}"
183
+ end
184
+ puts
185
+ end
186
+
187
+ puts "-- #{total_size/1024} Kbytes in #{emails.length} Requests."
188
+ end
189
+
190
+ ##
191
+ # Processes command line options in +args+
192
+
193
+ def self.process_args(args)
194
+ name = File.basename $0
195
+
196
+ options = {}
197
+ options[:Chdir] = '.'
198
+ options[:Daemon] = false
199
+ options[:Delay] = 60
200
+ options[:MaxAge] = 86400 * 7
201
+ options[:Once] = false
202
+ options[:RailsEnv] = ENV['RAILS_ENV']
203
+ options[:TableName] = 'Email'
204
+ options[:Pidfile] = options[:Chdir] + '/log/ar_sendmail.pid'
205
+
206
+ opts = OptionParser.new do |opts|
207
+ opts.banner = "Usage: #{name} [options]"
208
+ opts.separator ''
209
+
210
+ opts.separator "#{name} scans the email table for new messages and sends them to the"
211
+ opts.separator "website's configured SMTP host."
212
+ opts.separator ''
213
+ opts.separator "#{name} must be run from a Rails application's root."
214
+
215
+ opts.separator ''
216
+ opts.separator 'Sendmail options:'
217
+
218
+ opts.on("-b", "--batch-size BATCH_SIZE",
219
+ "Maximum number of emails to send per delay",
220
+ "Default: Deliver all available emails", Integer) do |batch_size|
221
+ options[:BatchSize] = batch_size
222
+ end
223
+
224
+ opts.on( "--delay DELAY",
225
+ "Delay between checks for new mail",
226
+ "in the database",
227
+ "Default: #{options[:Delay]}", Integer) do |delay|
228
+ options[:Delay] = delay
229
+ end
230
+
231
+ opts.on( "--max-age MAX_AGE",
232
+ "Maxmimum age for an email. After this",
233
+ "it will be removed from the queue.",
234
+ "Set to 0 to disable queue cleanup.",
235
+ "Default: #{options[:MaxAge]} seconds", Integer) do |max_age|
236
+ options[:MaxAge] = max_age
237
+ end
238
+
239
+ opts.on("-o", "--once",
240
+ "Only check for new mail and deliver once",
241
+ "Default: #{options[:Once]}") do |once|
242
+ options[:Once] = once
243
+ end
244
+
245
+ opts.on("-d", "--daemonize",
246
+ "Run as a daemon process",
247
+ "Default: #{options[:Daemon]}") do |daemon|
248
+ options[:Daemon] = true
249
+ end
250
+
251
+ opts.on("-p", "--pidfile PIDFILE",
252
+ "Set the pidfile location",
253
+ "Default: #{options[:Chdir]}#{options[:Pidfile]}", String) do |pidfile|
254
+ options[:Pidfile] = pidfile
255
+ end
256
+
257
+ opts.on( "--mailq",
258
+ "Display a list of emails waiting to be sent") do |mailq|
259
+ options[:MailQ] = true
260
+ end
261
+
262
+ opts.separator ''
263
+ opts.separator 'Setup Options:'
264
+
265
+ opts.on( "--create-migration",
266
+ "Prints a migration to add an Email table",
267
+ "to stdout") do |create|
268
+ options[:Migrate] = true
269
+ end
270
+
271
+ opts.on( "--create-model",
272
+ "Prints a model for an Email ActiveRecord",
273
+ "object to stdout") do |create|
274
+ options[:Model] = true
275
+ end
276
+
277
+ opts.separator ''
278
+ opts.separator 'Generic Options:'
279
+
280
+ opts.on("-c", "--chdir PATH",
281
+ "Use PATH for the application path",
282
+ "Default: #{options[:Chdir]}") do |path|
283
+ usage opts, "#{path} is not a directory" unless File.directory? path
284
+ usage opts, "#{path} is not readable" unless File.readable? path
285
+ options[:Chdir] = path
286
+ end
287
+
288
+ opts.on("-e", "--environment RAILS_ENV",
289
+ "Set the RAILS_ENV constant",
290
+ "Default: #{options[:RailsEnv]}") do |env|
291
+ options[:RailsEnv] = env
292
+ end
293
+
294
+ opts.on("-t", "--table-name TABLE_NAME",
295
+ "Name of table holding emails",
296
+ "Used for both sendmail and",
297
+ "migration creation",
298
+ "Default: #{options[:TableName]}") do |name|
299
+ options[:TableName] = name
300
+ end
301
+
302
+ opts.on("-v", "--[no-]verbose",
303
+ "Be verbose",
304
+ "Default: #{options[:Verbose]}") do |verbose|
305
+ options[:Verbose] = verbose
306
+ end
307
+
308
+ opts.on("-h", "--help",
309
+ "You're looking at it") do
310
+ usage opts
311
+ end
312
+
313
+ opts.on("--version", "Version of ARMailer") do
314
+ usage "ar_mailer #{VERSION} (adzap fork)"
315
+ end
316
+
317
+ opts.separator ''
318
+ end
319
+
320
+ opts.parse! args
321
+
322
+ return options if options.include? :Migrate or options.include? :Model
323
+
324
+ ENV['RAILS_ENV'] = options[:RailsEnv]
325
+
326
+ Dir.chdir options[:Chdir] do
327
+ begin
328
+ require 'config/environment'
329
+ rescue LoadError
330
+ usage opts, <<-EOF
331
+ #{name} must be run from a Rails application's root to deliver email.
332
+ #{Dir.pwd} does not appear to be a Rails application root.
333
+ EOF
334
+ end
335
+ end
336
+
337
+ return options
338
+ end
339
+
340
+ ##
341
+ # Processes +args+ and runs as appropriate
342
+
343
+ def self.run(args = ARGV)
344
+ options = process_args args
345
+
346
+ if options.include? :Migrate then
347
+ create_migration options[:TableName]
348
+ exit
349
+ elsif options.include? :Model then
350
+ create_model options[:TableName]
351
+ exit
352
+ elsif options.include? :MailQ then
353
+ mailq options[:TableName]
354
+ exit
355
+ end
356
+
357
+ if options[:Daemon] then
358
+ require 'webrick/server'
359
+ @@pid_file = File.expand_path(options[:Pidfile], options[:Chdir])
360
+ if File.exists? @@pid_file
361
+ # check to see if process is actually running
362
+ pid = ''
363
+ File.open(@@pid_file, 'r') {|f| pid = f.read.chomp }
364
+ if system("ps -p #{pid} | grep #{pid}") # returns true if process is running, o.w. false
365
+ $stderr.puts "Warning: The pid file #{@@pid_file} exists and ar_sendmail is running. Shutting down."
366
+ exit
367
+ else
368
+ # not running, so remove existing pid file and continue
369
+ self.remove_pid_file
370
+ $stderr.puts "ar_sendmail is not running. Removing existing pid file and starting up..."
371
+ end
372
+ end
373
+ WEBrick::Daemon.start
374
+ File.open(@@pid_file, 'w') {|f| f.write("#{Process.pid}\n")}
375
+ end
376
+
377
+ new(options).run
378
+
379
+ rescue SystemExit
380
+ raise
381
+ rescue SignalException
382
+ exit
383
+ rescue Exception => e
384
+ $stderr.puts "Unhandled exception #{e.message}(#{e.class}):"
385
+ $stderr.puts "\t#{e.backtrace.join "\n\t"}"
386
+ exit 1
387
+ end
388
+
389
+ ##
390
+ # Prints a usage message to $stderr using +opts+ and exits
391
+
392
+ def self.usage(opts, message = nil)
393
+ if message then
394
+ $stderr.puts message
395
+ $stderr.puts
396
+ end
397
+
398
+ $stderr.puts opts
399
+ exit 1
400
+ end
401
+
402
+ ##
403
+ # Creates a new ARSendmail.
404
+ #
405
+ # Valid options are:
406
+ # <tt>:BatchSize</tt>:: Maximum number of emails to send per delay
407
+ # <tt>:Delay</tt>:: Delay between deliver attempts
408
+ # <tt>:TableName</tt>:: Table name that stores the emails
409
+ # <tt>:Once</tt>:: Only attempt to deliver emails once when run is called
410
+ # <tt>:Verbose</tt>:: Be verbose.
411
+
412
+ def initialize(options = {})
413
+ options[:Delay] ||= 60
414
+ options[:TableName] ||= 'Email'
415
+ options[:MaxAge] ||= 86400 * 7
416
+
417
+ @batch_size = options[:BatchSize]
418
+ @delay = options[:Delay]
419
+ @email_class = options[:TableName].constantize
420
+ @once = options[:Once]
421
+ @verbose = options[:Verbose]
422
+ @max_age = options[:MaxAge]
423
+
424
+ @failed_auth_count = 0
425
+ end
426
+
427
+ ##
428
+ # Removes emails that have lived in the queue for too long. If max_age is
429
+ # set to 0, no emails will be removed.
430
+
431
+ def cleanup
432
+ return if @max_age == 0
433
+ timeout = Time.now - @max_age
434
+ conditions = ['last_send_attempt > 0 and created_at < ?', timeout]
435
+ mail = @email_class.update_all({:failed => true}, conditions)
436
+
437
+ log "#{self.class}#cleanup expired #{mail} emails from the queue"
438
+ end
439
+
440
+ ##
441
+ # Delivers +emails+ to ActionMailer's SMTP server and destroys them.
442
+
443
+ def deliver(emails)
444
+ log "#{self.class}#deliver Delivering #{emails.size} emails through '#{smtp_settings[:address]}' as '#{(smtp_settings[:user] || smtp_settings[:user_name])}'"
445
+ settings = [
446
+ smtp_settings[:domain],
447
+ (smtp_settings[:user] || smtp_settings[:user_name]),
448
+ smtp_settings[:password],
449
+ smtp_settings[:authentication]
450
+ ]
451
+
452
+ smtp = Net::SMTP.new(smtp_settings[:address], smtp_settings[:port])
453
+ if smtp.respond_to?(:enable_starttls_auto) # NOTE: Ruby 1.8.7+ has TLS support built in (dvd, 11-05-2009)
454
+ smtp.enable_starttls_auto unless smtp_settings[:tls] == false
455
+ else
456
+ settings << smtp_settings[:tls]
457
+ end
458
+
459
+ smtp.start(*settings) do |session|
460
+ @failed_auth_count = 0
461
+ until emails.empty? do
462
+ email = emails.shift
463
+ email.last_send_attempt = Time.now.to_i
464
+ email.increment :attempts
465
+ begin
466
+ res = session.send_message email.mail, email.from, email.to
467
+ email.failed = false
468
+ email.sent_at = Time.now
469
+
470
+ log "#{self.class}#deliver sent email %011d from %s to %s: %p" %
471
+ [email.id, email.from, email.to, res]
472
+ rescue Net::SMTPFatalError => e
473
+ log "#{self.class}#deliver 5xx error sending email %d, removing from queue: %p(%s):\n\t%s" % [email.id, e.message, e.class, e.backtrace.join("\n\t")]
474
+ email.last_error = "Exception: #{e.class}\n\nMessage:\n#{e.message}\n\nBacktrace:\n#{e.backtrace.join("\n\t")}"
475
+ email.failed = true
476
+ session.reset
477
+ rescue Net::SMTPServerBusy => e
478
+ log "#{self.class}#deliver server too busy, sleeping #{@delay} seconds"
479
+ email.last_error = "Exception: #{e.class}\n\nMessage:\n#{e.message}\n\nBacktrace:\n#{e.backtrace.join("\n\t")}"
480
+ email.save! # TODO: the return here means we have to save the email before, so the attempts count stays correct (dvd, 11-05-2009)
481
+ sleep delay
482
+ return
483
+ rescue Net::SMTPUnknownError, Net::SMTPSyntaxError, TimeoutError => e
484
+ email.last_error = "Exception: #{e.class}\n\nMessage:\n#{e.message}\n\nBacktrace:\n#{e.backtrace.join("\n\t")}"
485
+ log "#{self.class}#deliver error sending email %d: %p(%s):\n\t%s" %
486
+ [email.id, e.message, e.class, e.backtrace.join("\n\t")]
487
+ session.reset
488
+ end
489
+ email.save!
490
+ end
491
+
492
+ end
493
+ rescue Net::SMTPAuthenticationError => e
494
+ @failed_auth_count += 1
495
+ if @failed_auth_count >= MAX_AUTH_FAILURES then
496
+ log "#{self.class}#deliver authentication error, giving up: #{e.message}"
497
+ raise e
498
+ else
499
+ log "#{self.class}#deliver authentication error, retrying: #{e.message}"
500
+ end
501
+ sleep delay
502
+ rescue Net::SMTPServerBusy, SystemCallError, OpenSSL::SSL::SSLError
503
+ # ignore SMTPServerBusy/EPIPE/ECONNRESET from Net::SMTP.start's ensure
504
+ end
505
+
506
+ ##
507
+ # Prepares ar_sendmail for exiting
508
+
509
+ def do_exit
510
+ log "#{self.class}#deliver caught signal, shutting down"
511
+ self.class.remove_pid_file
512
+ exit
513
+ end
514
+
515
+ ##
516
+ # Returns emails in email_class that haven't had a delivery attempt in the
517
+ # last 300 seconds.
518
+
519
+ def find_emails
520
+ # options = { :conditions => ['last_send_attempt < ? AND failed = 0', Time.now.to_i - 300] }
521
+ options = { :conditions => {:sent_at => nil, :failed => false}}
522
+ options[:limit] = batch_size unless batch_size.nil?
523
+ mail = @email_class.find :all, options
524
+
525
+ log "#{self.class}#deliver found #{mail.length} emails to send"
526
+ mail
527
+ end
528
+
529
+ ##
530
+ # Installs signal handlers to gracefully exit.
531
+
532
+ def install_signal_handlers
533
+ trap 'TERM' do do_exit end
534
+ trap 'INT' do do_exit end
535
+ end
536
+
537
+ ##
538
+ # Logs +message+ if verbose
539
+
540
+ def log(message)
541
+ $stderr.puts message if @verbose
542
+ ActionMailer::Base.logger.info "ar_sendmail ==> #{message}"
543
+ end
544
+
545
+ ##
546
+ # Scans for emails and delivers them every delay seconds. Only returns if
547
+ # once is true.
548
+
549
+ def run
550
+ install_signal_handlers
551
+
552
+ loop do
553
+ now = Time.now
554
+ begin
555
+ cleanup
556
+ emails = find_emails
557
+ deliver(emails) unless emails.empty?
558
+ rescue ActiveRecord::Transactions::TransactionError
559
+ end
560
+ break if @once
561
+ sleep @delay if now + @delay > Time.now
562
+ end
563
+ end
564
+
565
+ ##
566
+ # Proxy to ActionMailer::Base::smtp_settings. See
567
+ # http://api.rubyonrails.org/classes/ActionMailer/Base.html
568
+ # for instructions on how to configure ActionMailer's SMTP server.
569
+ #
570
+ # Falls back to ::server_settings if ::smtp_settings doesn't exist for
571
+ # backwards compatibility.
572
+
573
+ def smtp_settings
574
+ ActionMailer::Base.smtp_settings rescue ActionMailer::Base.server_settings
575
+ end
576
+
577
+ end