zendesk-ar_mailer 1.4.5

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.
@@ -0,0 +1,81 @@
1
+ = 1.4.3
2
+
3
+ * Bugs fixed
4
+ * Replaced mistaken call to log when removing pid file artifact for
5
+ non-running daemon
6
+
7
+ = 1.4.2
8
+
9
+ * New Features
10
+ * Added Ruby based linux init.d script for handling daemon startup using yaml
11
+ config file. See files share/linux/ar_sendmail and ar_sendmail.conf
12
+ * Bugs fixed
13
+ * Proper handling for relative and absolute paths for the pid file
14
+ * Removed hoe dependency since we need the explicit gemspec file for github and
15
+ not deploying to RubyForge its not as useful.
16
+ * Moved old BSD rc.d script to share/bsd folder
17
+ * Updated README with github gem install, docs and init script info
18
+
19
+ = 1.4.1
20
+
21
+ * Bugs fixed
22
+ * Daemon failed on startup fixed with expanding full path of pid file
23
+
24
+ = 1.4.0
25
+
26
+ * Forked gem and published on GitHub (gem sources -a http://gems.github.com)
27
+ * New Features
28
+ * Added pid file creation on daemonize with command line option to specify pid filename [Dylan Egan]
29
+
30
+ = 1.3.1
31
+
32
+ * Fix bug #12530, gmail causes SSL errors. Submitted by Kyle Maxwell
33
+ and Alex Ostleitner.
34
+ * Try ActionMailer::Base::server_settings then ::smtp_settings. Fixes
35
+ bug #12516. Submitted by Alex Ostleitner.
36
+
37
+ = 1.3.0
38
+
39
+ * New Features
40
+ * Added automatic mail queue cleanup.
41
+ * MAY CAUSE LOSS OF DATA. If you haven't run ar_sendmail within
42
+ the expiry time, set it to 0.
43
+ * Bugs fixed
44
+ * Authentication errors are now handled by retrying once.
45
+
46
+ = 1.2.0
47
+
48
+ * Bugs fixed
49
+ * Handle SMTPServerBusy by backing off @delay seconds then re-queueing
50
+ * Allow email delivery class to be set in ARMailer.
51
+ * ar_sendmail --mailq works with --table-name now.
52
+ * Miscellaneous Updates
53
+ * Added documentation to require 'action_mailer/ar_mailer' in
54
+ instructions.
55
+ * Moved to ZSS p4 repository
56
+ * Supports TLS now. Requested by Dave Thomas. smtp_tls.rb from Kyle
57
+ Maxwell & etc.
58
+
59
+ = 1.1.0
60
+
61
+ * Features
62
+ * Added --chdir to set rails directory
63
+ * Added --environment to set RAILS_ENV
64
+ * Exits cleanly on TERM or INT signals
65
+ * Added FreeBSD rc.d script
66
+ * Exceptions during SMTP sending are now logged
67
+ * No longer waits if sending email took too long
68
+ * Bugs fixed
69
+ * Fixed last send attempt in --mailq
70
+ * Better SMTP error handling
71
+ * Messages are removed from the queue on 5xx errors
72
+ * Added Net::SMTP.reset to avoid needing to recreate the connection
73
+
74
+ = 1.0.1
75
+
76
+ * Bugs fixed
77
+ * From and to of email destination were swapped
78
+
79
+ = 1.0.0
80
+
81
+ * Birthday
@@ -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.
@@ -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
@@ -0,0 +1,70 @@
1
+ = ar_mailer
2
+
3
+ A two-phase delivery agent for ActionMailer
4
+
5
+ Rubyforge Project:
6
+
7
+ http://rubyforge.org/projects/seattlerb
8
+
9
+ Documentation:
10
+
11
+ http://seattlerb.org/ar_mailer
12
+
13
+ and for forked additions
14
+
15
+ http://github.com/adzap/ar_mailer/wikis
16
+
17
+ Bugs:
18
+
19
+ http://rubyforge.org/tracker/?func=add&group_id=1513&atid=5921
20
+
21
+ == About
22
+
23
+ Even delivering email to the local machine may take too long when you have to
24
+ send hundreds of messages. ar_mailer allows you to store messages into the
25
+ database for later delivery by a separate process, ar_sendmail.
26
+
27
+ == Installing ar_mailer (forked)
28
+
29
+ Install the gem from GitHub gems server:
30
+
31
+ First, if you haven't already
32
+
33
+ $ sudo gem sources -a http://gems.github.com
34
+
35
+ Then
36
+
37
+ $ sudo gem install adzap-ar_mailer
38
+
39
+ See ActionMailer::ARMailer for instructions on converting to ARMailer.
40
+
41
+ See ar_sendmail -h for options to ar_sendmail.
42
+
43
+ NOTE: You may need to delete an smtp_tls.rb file if you have one lying
44
+ around. ar_mailer supplies it own.
45
+
46
+ === Getting context aware logging
47
+
48
+ If you want to log some extra information that you can use to tie a specific send email to
49
+ something in your application, do this:
50
+
51
+ 1. Add a +context+ column to your emails table, eg.:
52
+
53
+ add_column :emails, :context, :string
54
+
55
+ 2. Set the information you want ar_sendmail to log alongside the message-id in your mailer class:
56
+
57
+ headers(ActionMailer::ARMailer.context_header => 'Hello there')
58
+
59
+ This will print 'Hello there' in the line that also contains the message-id of the sent email.
60
+
61
+ === init.d/rc.d scripts
62
+
63
+ For Linux both script and demo config files are in share/linux.
64
+ See ar_sendmail.conf for setting up your config. Copy the ar_sendmail file
65
+ to /etc/init.d/ and make it executable. Then for Debian based distros run
66
+ 'sudo update-rc.d ar_sendmail defaults' and it should work. Make sure you have
67
+ the config file /etc/ar_sendmail.conf in place before starting.
68
+
69
+ For FreeBSD or NetBSD script is share/bsd/ar_sendmail. This is old and does not
70
+ support the config file unless someone wants to submit a patch.
@@ -0,0 +1,60 @@
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"]
15
+ s.date = %q{2008-07-04}
16
+ s.default_executable = %q{ar_sendmail}
17
+ 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.}
18
+ s.email = %q{drbrain@segment7.net}
19
+ s.executables = ["ar_sendmail"]
20
+ s.extra_rdoc_files = ["History.txt", "LICENSE.txt", "Manifest.txt", "README.txt"]
21
+ s.files = ["History.txt", "LICENSE.txt", "Manifest.txt", "README.txt", "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"]
22
+ s.has_rdoc = true
23
+ s.homepage = %q{http://seattlerb.org/ar_mailer}
24
+ s.rdoc_options = ["--main", "README.txt"]
25
+ s.require_paths = ["lib"]
26
+ s.rubyforge_project = %q{seattlerb}
27
+ s.rubygems_version = %q{1.2.0}
28
+ s.summary = %q{A two-phase delivery agent for ActionMailer}
29
+ s.test_files = ["test/test_armailer.rb", "test/test_arsendmail.rb"]
30
+ end
31
+
32
+ Rake::GemPackageTask.new(ar_mailer_gemspec) do |pkg|
33
+ pkg.gem_spec = ar_mailer_gemspec
34
+ end
35
+
36
+ namespace :gem do
37
+ namespace :spec do
38
+ desc "Update ar_mailer.gemspec"
39
+ task :generate do
40
+ File.open("ar_mailer.gemspec", "w") do |f|
41
+ f.puts(ar_mailer_gemspec.to_ruby)
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ desc "Build packages and install"
48
+ task :install => :package do
49
+ sh %{sudo gem install --local pkg/ar_mailer-#{ActionMailer::ARSendmail::VERSION}}
50
+ end
51
+
52
+ desc 'Default: run unit tests.'
53
+ task :default => :test
54
+
55
+ desc 'Test the ar_mailer gem.'
56
+ Rake::TestTask.new(:test) do |t|
57
+ t.libs << 'lib' << 'test'
58
+ t.pattern = 'test/**/test_*.rb'
59
+ t.verbose = true
60
+ end
@@ -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,111 @@
1
+ require 'action_mailer'
2
+
3
+ ##
4
+ # Adds sending email through an ActiveRecord table as a delivery method for
5
+ # ActionMailer.
6
+ #
7
+ # == Converting to ActionMailer::ARMailer
8
+ #
9
+ # Go to your Rails project:
10
+ #
11
+ # $ cd your_rails_project
12
+ #
13
+ # Create a new migration:
14
+ #
15
+ # $ ar_sendmail --create-migration
16
+ #
17
+ # You'll need to redirect this into a file. If you want a different name
18
+ # provide the --table-name option.
19
+ #
20
+ # Create a new model:
21
+ #
22
+ # $ ar_sendmail --create-model
23
+ #
24
+ # You'll need to redirect this into a file. If you want a different name
25
+ # provide the --table-name option.
26
+ #
27
+ # Change your email classes to inherit from ActionMailer::ARMailer instead of
28
+ # ActionMailer::Base:
29
+ #
30
+ # --- app/model/emailer.rb.orig 2006-08-10 13:16:33.000000000 -0700
31
+ # +++ app/model/emailer.rb 2006-08-10 13:16:43.000000000 -0700
32
+ # @@ -1,4 +1,4 @@
33
+ # -class Emailer < ActionMailer::Base
34
+ # +class Emailer < ActionMailer::ARMailer
35
+ #
36
+ # def comment_notification(comment)
37
+ # from comment.author.email
38
+ #
39
+ # You'll need to be sure to set the From address for your emails. Something
40
+ # like:
41
+ #
42
+ # def list_send(recipient)
43
+ # from 'no_reply@example.com'
44
+ # # ...
45
+ #
46
+ # Edit config/environment.rb and require ar_mailer.rb:
47
+ #
48
+ # require 'action_mailer/ar_mailer'
49
+ #
50
+ # Edit config/environments/production.rb and set the delivery agent:
51
+ #
52
+ # $ grep delivery_method config/environments/production.rb
53
+ # ActionMailer::Base.delivery_method = :activerecord
54
+ #
55
+ # Run ar_sendmail:
56
+ #
57
+ # $ ar_sendmail
58
+ #
59
+ # You can also run it from cron with -o, or as a daemon with -d.
60
+ #
61
+ # See <tt>ar_sendmail -h</tt> for full details.
62
+ #
63
+ # == Alternate Mail Storage
64
+ #
65
+ # If you want to set the ActiveRecord model that emails will be stored in,
66
+ # see ActionMailer::ARMailer::email_class=
67
+
68
+ class ActionMailer::ARMailer < ActionMailer::Base
69
+
70
+ @@email_class = Email
71
+
72
+ ##
73
+ # Current email class for deliveries.
74
+
75
+ def self.email_class
76
+ @@email_class
77
+ end
78
+
79
+ ##
80
+ # Sets the email class for deliveries.
81
+
82
+ def self.email_class=(klass)
83
+ @@email_class = klass
84
+ end
85
+
86
+ @@context_header = 'X-Delivery-Context'
87
+
88
+ def self.context_header
89
+ @@context_header
90
+ end
91
+
92
+ ##
93
+ # Adds +mail+ to the Email table. Only the first From address for +mail+ is
94
+ # used.
95
+
96
+ def perform_delivery_activerecord(mail)
97
+ mail.destinations.each do |destination|
98
+ # If the email has the context header set, assume that the caller added a context column
99
+ if @@context_header && mail.key?(@@context_header) && !mail[@@context_header].body.strip.blank?
100
+ @@email_class.create(
101
+ :mail => mail.encoded, :to => destination,
102
+ :from => mail.from.first, :context => mail[@@context_header].body.strip
103
+ )
104
+ else
105
+ @@email_class.create(:mail => mail.encoded, :to => destination, :from => mail.from.first)
106
+ end
107
+ end
108
+ end
109
+
110
+ end
111
+
@@ -0,0 +1,558 @@
1
+ require 'optparse'
2
+ require 'net/smtp'
3
+ require 'smtp_tls'
4
+ require 'rubygems'
5
+
6
+ class Object # :nodoc:
7
+ unless respond_to? :path2class then
8
+ def self.path2class(path)
9
+ path.split(/::/).inject self do |k,n| k.const_get n end
10
+ end
11
+ end
12
+ end
13
+
14
+ ##
15
+ # Hack in RSET
16
+
17
+ module Net # :nodoc:
18
+ class SMTP # :nodoc:
19
+
20
+ unless instance_methods.include? 'reset' then
21
+ ##
22
+ # Resets the SMTP connection.
23
+
24
+ def reset
25
+ getok 'RSET'
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+
32
+ module ActionMailer; end # :nodoc:
33
+
34
+ ##
35
+ # ActionMailer::ARSendmail delivers email from the email table to the
36
+ # SMTP server configured in your application's config/environment.rb.
37
+ # ar_sendmail does not work with sendmail delivery.
38
+ #
39
+ # ar_mailer can deliver to SMTP with TLS using smtp_tls.rb borrowed from Kyle
40
+ # Maxwell's action_mailer_optional_tls plugin. Simply set the :tls option in
41
+ # ActionMailer::Base's smtp_settings to true to enable TLS.
42
+ #
43
+ # See ar_sendmail -h for the full list of supported options.
44
+ #
45
+ # The interesting options are:
46
+ # * --daemon
47
+ # * --mailq
48
+ # * --create-migration
49
+ # * --create-model
50
+ # * --table-name
51
+
52
+ class ActionMailer::ARSendmail
53
+
54
+ ##
55
+ # The version of ActionMailer::ARSendmail you are running.
56
+
57
+ VERSION = '1.4.4'
58
+
59
+ ##
60
+ # Maximum number of times authentication will be consecutively retried
61
+
62
+ MAX_AUTH_FAILURES = 2
63
+
64
+ ##
65
+ # Email delivery attempts per run
66
+
67
+ attr_accessor :batch_size
68
+
69
+ ##
70
+ # Seconds to delay between runs
71
+
72
+ attr_accessor :delay
73
+
74
+ ##
75
+ # Maximum age of emails in seconds before they are removed from the queue.
76
+
77
+ attr_accessor :max_age
78
+
79
+ ##
80
+ # Be verbose
81
+
82
+ attr_accessor :verbose
83
+
84
+ ##
85
+ # ActiveRecord class that holds emails
86
+
87
+ attr_reader :email_class
88
+
89
+ ##
90
+ # True if only one delivery attempt will be made per call to run
91
+
92
+ attr_reader :once
93
+
94
+ ##
95
+ # Times authentication has failed
96
+
97
+ attr_accessor :failed_auth_count
98
+
99
+ @@pid_file = nil
100
+
101
+ def self.remove_pid_file
102
+ if @@pid_file
103
+ require 'shell'
104
+ sh = Shell.new
105
+ sh.rm @@pid_file
106
+ end
107
+ end
108
+
109
+ ##
110
+ # Creates a new migration using +table_name+ and prints it on stdout.
111
+
112
+ def self.create_migration(table_name)
113
+ require 'active_support'
114
+ puts <<-EOF
115
+ class Add#{table_name.classify} < ActiveRecord::Migration
116
+ def self.up
117
+ create_table :#{table_name.tableize} do |t|
118
+ t.column :from, :string
119
+ t.column :to, :string
120
+ t.column :last_send_attempt, :integer, :default => 0
121
+ t.column :mail, :text
122
+ t.column :created_on, :datetime
123
+ end
124
+ end
125
+
126
+ def self.down
127
+ drop_table :#{table_name.tableize}
128
+ end
129
+ end
130
+ EOF
131
+ end
132
+
133
+ ##
134
+ # Creates a new model using +table_name+ and prints it on stdout.
135
+
136
+ def self.create_model(table_name)
137
+ require 'active_support'
138
+ puts <<-EOF
139
+ class #{table_name.classify} < ActiveRecord::Base
140
+ end
141
+ EOF
142
+ end
143
+
144
+ ##
145
+ # Prints a list of unsent emails and the last delivery attempt, if any.
146
+ #
147
+ # If ActiveRecord::Timestamp is not being used the arrival time will not be
148
+ # known. See http://api.rubyonrails.org/classes/ActiveRecord/Timestamp.html
149
+ # to learn how to enable ActiveRecord::Timestamp.
150
+
151
+ def self.mailq(table_name)
152
+ klass = table_name.split('::').inject(Object) { |k,n| k.const_get n }
153
+ emails = klass.find :all
154
+
155
+ if emails.empty? then
156
+ puts "Mail queue is empty"
157
+ return
158
+ end
159
+
160
+ total_size = 0
161
+
162
+ puts "-Queue ID- --Size-- ----Arrival Time---- -Sender/Recipient-------"
163
+ emails.each do |email|
164
+ size = email.mail.length
165
+ total_size += size
166
+
167
+ create_timestamp = email.created_on rescue
168
+ email.created_at rescue
169
+ Time.at(email.created_date) rescue # for Robot Co-op
170
+ nil
171
+
172
+ created = if create_timestamp.nil? then
173
+ ' Unknown'
174
+ else
175
+ create_timestamp.strftime '%a %b %d %H:%M:%S'
176
+ end
177
+
178
+ puts "%10d %8d %s %s" % [email.id, size, created, email.from]
179
+ if email.last_send_attempt > 0 then
180
+ puts "Last send attempt: #{Time.at email.last_send_attempt}"
181
+ end
182
+ puts " #{email.to}"
183
+ puts
184
+ end
185
+
186
+ puts "-- #{total_size/1024} Kbytes in #{emails.length} Requests."
187
+ end
188
+
189
+ ##
190
+ # Processes command line options in +args+
191
+
192
+ def self.process_args(args)
193
+ name = File.basename $0
194
+
195
+ options = {}
196
+ options[:Chdir] = '.'
197
+ options[:Daemon] = false
198
+ options[:Delay] = 60
199
+ options[:MaxAge] = 86400 * 7
200
+ options[:Once] = false
201
+ options[:RailsEnv] = ENV['RAILS_ENV']
202
+ options[:TableName] = 'Email'
203
+ options[:Pidfile] = options[:Chdir] + '/log/ar_sendmail.pid'
204
+
205
+ opts = OptionParser.new do |opts|
206
+ opts.banner = "Usage: #{name} [options]"
207
+ opts.separator ''
208
+
209
+ opts.separator "#{name} scans the email table for new messages and sends them to the"
210
+ opts.separator "website's configured SMTP host."
211
+ opts.separator ''
212
+ opts.separator "#{name} must be run from a Rails application's root."
213
+
214
+ opts.separator ''
215
+ opts.separator 'Sendmail options:'
216
+
217
+ opts.on("-b", "--batch-size BATCH_SIZE",
218
+ "Maximum number of emails to send per delay",
219
+ "Default: Deliver all available emails", Integer) do |batch_size|
220
+ options[:BatchSize] = batch_size
221
+ end
222
+
223
+ opts.on( "--delay DELAY",
224
+ "Delay between checks for new mail",
225
+ "in the database",
226
+ "Default: #{options[:Delay]}", Integer) do |delay|
227
+ options[:Delay] = delay
228
+ end
229
+
230
+ opts.on( "--max-age MAX_AGE",
231
+ "Maxmimum age for an email. After this",
232
+ "it will be removed from the queue.",
233
+ "Set to 0 to disable queue cleanup.",
234
+ "Default: #{options[:MaxAge]} seconds", Integer) do |max_age|
235
+ options[:MaxAge] = max_age
236
+ end
237
+
238
+ opts.on("-o", "--once",
239
+ "Only check for new mail and deliver once",
240
+ "Default: #{options[:Once]}") do |once|
241
+ options[:Once] = once
242
+ end
243
+
244
+ opts.on("-d", "--daemonize",
245
+ "Run as a daemon process",
246
+ "Default: #{options[:Daemon]}") do |daemon|
247
+ options[:Daemon] = true
248
+ end
249
+
250
+ opts.on("-p", "--pidfile PIDFILE",
251
+ "Set the pidfile location",
252
+ "Default: #{options[:Chdir]}#{options[:Pidfile]}", String) do |pidfile|
253
+ options[:Pidfile] = pidfile
254
+ end
255
+
256
+ opts.on( "--mailq",
257
+ "Display a list of emails waiting to be sent") do |mailq|
258
+ options[:MailQ] = true
259
+ end
260
+
261
+ opts.separator ''
262
+ opts.separator 'Setup Options:'
263
+
264
+ opts.on( "--create-migration",
265
+ "Prints a migration to add an Email table",
266
+ "to stdout") do |create|
267
+ options[:Migrate] = true
268
+ end
269
+
270
+ opts.on( "--create-model",
271
+ "Prints a model for an Email ActiveRecord",
272
+ "object to stdout") do |create|
273
+ options[:Model] = true
274
+ end
275
+
276
+ opts.separator ''
277
+ opts.separator 'Generic Options:'
278
+
279
+ opts.on("-c", "--chdir PATH",
280
+ "Use PATH for the application path",
281
+ "Default: #{options[:Chdir]}") do |path|
282
+ usage opts, "#{path} is not a directory" unless File.directory? path
283
+ usage opts, "#{path} is not readable" unless File.readable? path
284
+ options[:Chdir] = path
285
+ end
286
+
287
+ opts.on("-e", "--environment RAILS_ENV",
288
+ "Set the RAILS_ENV constant",
289
+ "Default: #{options[:RailsEnv]}") do |env|
290
+ options[:RailsEnv] = env
291
+ end
292
+
293
+ opts.on("-t", "--table-name TABLE_NAME",
294
+ "Name of table holding emails",
295
+ "Used for both sendmail and",
296
+ "migration creation",
297
+ "Default: #{options[:TableName]}") do |name|
298
+ options[:TableName] = name
299
+ end
300
+
301
+ opts.on("-v", "--[no-]verbose",
302
+ "Be verbose",
303
+ "Default: #{options[:Verbose]}") do |verbose|
304
+ options[:Verbose] = verbose
305
+ end
306
+
307
+ opts.on("-h", "--help",
308
+ "You're looking at it") do
309
+ usage opts
310
+ end
311
+
312
+ opts.separator ''
313
+ end
314
+
315
+ opts.parse! args
316
+
317
+ return options if options.include? :Migrate or options.include? :Model
318
+
319
+ ENV['RAILS_ENV'] = options[:RailsEnv]
320
+
321
+ Dir.chdir options[:Chdir] do
322
+ begin
323
+ require 'config/environment'
324
+ rescue LoadError
325
+ usage opts, <<-EOF
326
+ #{name} must be run from a Rails application's root to deliver email.
327
+ #{Dir.pwd} does not appear to be a Rails application root.
328
+ EOF
329
+ end
330
+ end
331
+
332
+ return options
333
+ end
334
+
335
+ ##
336
+ # Processes +args+ and runs as appropriate
337
+
338
+ def self.run(args = ARGV)
339
+ options = process_args args
340
+
341
+ if options.include? :Migrate then
342
+ create_migration options[:TableName]
343
+ exit
344
+ elsif options.include? :Model then
345
+ create_model options[:TableName]
346
+ exit
347
+ elsif options.include? :MailQ then
348
+ mailq options[:TableName]
349
+ exit
350
+ end
351
+
352
+ if options[:Daemon] then
353
+ require 'webrick/server'
354
+ @@pid_file = File.expand_path(options[:Pidfile], options[:Chdir])
355
+ if File.exists? @@pid_file
356
+ # check to see if process is actually running
357
+ pid = ''
358
+ File.open(@@pid_file, 'r') {|f| pid = f.read.chomp }
359
+ if system("ps -p #{pid} | grep #{pid}") # returns true if process is running, o.w. false
360
+ $stderr.puts "Warning: The pid file #{@@pid_file} exists and ar_sendmail is running. Shutting down."
361
+ exit
362
+ else
363
+ # not running, so remove existing pid file and continue
364
+ self.remove_pid_file
365
+ $stderr.puts "ar_sendmail is not running. Removing existing pid file and starting up..."
366
+ end
367
+ end
368
+ WEBrick::Daemon.start
369
+ File.open(@@pid_file, 'w') {|f| f.write("#{Process.pid}\n")}
370
+ end
371
+
372
+ new(options).run
373
+
374
+ rescue SystemExit
375
+ raise
376
+ rescue SignalException
377
+ exit
378
+ rescue Exception => e
379
+ $stderr.puts "Unhandled exception #{e.message}(#{e.class}):"
380
+ $stderr.puts "\t#{e.backtrace.join "\n\t"}"
381
+ exit 1
382
+ end
383
+
384
+ ##
385
+ # Prints a usage message to $stderr using +opts+ and exits
386
+
387
+ def self.usage(opts, message = nil)
388
+ if message then
389
+ $stderr.puts message
390
+ $stderr.puts
391
+ end
392
+
393
+ $stderr.puts opts
394
+ exit 1
395
+ end
396
+
397
+ ##
398
+ # Creates a new ARSendmail.
399
+ #
400
+ # Valid options are:
401
+ # <tt>:BatchSize</tt>:: Maximum number of emails to send per delay
402
+ # <tt>:Delay</tt>:: Delay between deliver attempts
403
+ # <tt>:TableName</tt>:: Table name that stores the emails
404
+ # <tt>:Once</tt>:: Only attempt to deliver emails once when run is called
405
+ # <tt>:Verbose</tt>:: Be verbose.
406
+
407
+ def initialize(options = {})
408
+ options[:Delay] ||= 60
409
+ options[:TableName] ||= 'Email'
410
+ options[:MaxAge] ||= 86400 * 7
411
+
412
+ @batch_size = options[:BatchSize]
413
+ @delay = options[:Delay]
414
+ @email_class = Object.path2class options[:TableName]
415
+ @once = options[:Once]
416
+ @verbose = options[:Verbose]
417
+ @max_age = options[:MaxAge]
418
+
419
+ @failed_auth_count = 0
420
+ end
421
+
422
+ ##
423
+ # Removes emails that have lived in the queue for too long. If max_age is
424
+ # set to 0, no emails will be removed.
425
+
426
+ def cleanup
427
+ return if @max_age == 0
428
+ timeout = Time.now - @max_age
429
+ conditions = ['last_send_attempt > 0 and created_on < ?', timeout]
430
+ mail = @email_class.destroy_all conditions
431
+
432
+ log "expired #{mail.length} emails from the queue"
433
+ end
434
+
435
+ ##
436
+ # Delivers +emails+ to ActionMailer's SMTP server and destroys them.
437
+
438
+ def deliver(emails)
439
+ return if emails.empty?
440
+ user = smtp_settings[:user] || smtp_settings[:user_name]
441
+ Net::SMTP.start smtp_settings[:address], smtp_settings[:port],
442
+ smtp_settings[:domain], user,
443
+ smtp_settings[:password],
444
+ smtp_settings[:authentication],
445
+ smtp_settings[:tls] do |smtp|
446
+ @failed_auth_count = 0
447
+ until emails.empty? do
448
+ email = emails.shift
449
+ begin
450
+ res = smtp.send_message email.mail, email.from, email.to
451
+ email.destroy
452
+ if email.respond_to?(:context)
453
+ log "sent email %011d [%s] from %s to %s: %p" % [email.id, email.context, email.from, email.to, res]
454
+ else
455
+ log "sent email %011d from %s to %s: %p" % [email.id, email.from, email.to, res]
456
+ end
457
+ rescue Net::SMTPFatalError => e
458
+ log "5xx error sending email %d, removing from queue: %p(%s):\n\t%s" %
459
+ [email.id, e.message, e.class, e.backtrace.join("\n\t")]
460
+ email.destroy
461
+ smtp.reset
462
+ rescue Net::SMTPServerBusy => e
463
+ log "server too busy, sleeping #{@delay} seconds"
464
+ sleep delay
465
+ return
466
+ rescue Net::SMTPUnknownError, Net::SMTPSyntaxError, TimeoutError => e
467
+ email.last_send_attempt = Time.now.to_i
468
+ email.save rescue nil
469
+ log "error sending email %d: %p(%s):\n\t%s" %
470
+ [email.id, e.message, e.class, e.backtrace.join("\n\t")]
471
+ smtp.reset
472
+ end
473
+ end
474
+ end
475
+ rescue Net::SMTPAuthenticationError => e
476
+ @failed_auth_count += 1
477
+ if @failed_auth_count >= MAX_AUTH_FAILURES then
478
+ log "authentication error, giving up: #{e.message}"
479
+ raise e
480
+ else
481
+ log "authentication error, retrying: #{e.message}"
482
+ end
483
+ sleep delay
484
+ rescue Net::SMTPServerBusy, SystemCallError, OpenSSL::SSL::SSLError
485
+ # ignore SMTPServerBusy/EPIPE/ECONNRESET from Net::SMTP.start's ensure
486
+ end
487
+
488
+ ##
489
+ # Prepares ar_sendmail for exiting
490
+
491
+ def do_exit
492
+ log "caught signal, shutting down"
493
+ self.class.remove_pid_file
494
+ exit
495
+ end
496
+
497
+ ##
498
+ # Returns emails in email_class that haven't had a delivery attempt in the
499
+ # last 300 seconds.
500
+
501
+ def find_emails
502
+ options = { :conditions => ['last_send_attempt < ?', Time.now.to_i - 300] }
503
+ options[:limit] = batch_size unless batch_size.nil?
504
+ mail = @email_class.find :all, options
505
+
506
+ log "found #{mail.length} emails to send"
507
+ mail
508
+ end
509
+
510
+ ##
511
+ # Installs signal handlers to gracefully exit.
512
+
513
+ def install_signal_handlers
514
+ trap 'TERM' do do_exit end
515
+ trap 'INT' do do_exit end
516
+ end
517
+
518
+ ##
519
+ # Logs +message+ if verbose
520
+
521
+ def log(message)
522
+ $stderr.puts message if @verbose
523
+ ActionMailer::Base.logger.info "ar_sendmail #{Time.now}: #{message}"
524
+ end
525
+
526
+ ##
527
+ # Scans for emails and delivers them every delay seconds. Only returns if
528
+ # once is true.
529
+
530
+ def run
531
+ install_signal_handlers
532
+
533
+ loop do
534
+ now = Time.now
535
+ begin
536
+ cleanup
537
+ deliver find_emails
538
+ rescue ActiveRecord::Transactions::TransactionError
539
+ end
540
+ break if @once
541
+ sleep @delay if now + @delay > Time.now
542
+ end
543
+ end
544
+
545
+ ##
546
+ # Proxy to ActionMailer::Base::smtp_settings. See
547
+ # http://api.rubyonrails.org/classes/ActionMailer/Base.html
548
+ # for instructions on how to configure ActionMailer's SMTP server.
549
+ #
550
+ # Falls back to ::server_settings if ::smtp_settings doesn't exist for
551
+ # backwards compatibility.
552
+
553
+ def smtp_settings
554
+ ActionMailer::Base.smtp_settings rescue ActionMailer::Base.server_settings
555
+ end
556
+
557
+ end
558
+