ynw-ar_mailer 0.0.1
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 +135 -0
- data/LICENSE.txt +28 -0
- data/README.rdoc +165 -0
- data/Rakefile +54 -0
- data/bin/ar_sendmail +6 -0
- data/generators/ar_mailer/ar_mailer_generator.rb +25 -0
- data/generators/ar_mailer/templates/migration.rb +16 -0
- data/generators/ar_mailer/templates/model.rb +2 -0
- data/lib/action_mailer/ar_mailer.rb +31 -0
- data/lib/action_mailer/ar_sendmail.rb +563 -0
- data/lib/smtp_tls.rb +105 -0
- data/share/bsd/ar_sendmail +30 -0
- data/share/linux/ar_sendmail +78 -0
- data/share/linux/ar_sendmail.conf +30 -0
- data/test/resources/action_mailer.rb +197 -0
- data/test/test_armailer.rb +46 -0
- data/test/test_arsendmail.rb +553 -0
- data/test/test_helper.rb +9 -0
- metadata +78 -0
@@ -0,0 +1,563 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'net/smtp'
|
3
|
+
require File.join(File.dirname(__FILE__), '..', '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
|
+
##
|
25
|
+
# ActionMailer::ARSendmail delivers email from the email table to the
|
26
|
+
# SMTP server configured in your application's config/environment.rb.
|
27
|
+
# ar_sendmail does not work with sendmail delivery.
|
28
|
+
#
|
29
|
+
# ar_mailer can deliver to SMTP with TLS using smtp_tls.rb borrowed from Kyle
|
30
|
+
# Maxwell's action_mailer_optional_tls plugin. Simply set the :tls option in
|
31
|
+
# ActionMailer::Base's smtp_settings to true to enable TLS.
|
32
|
+
#
|
33
|
+
# See ar_sendmail -h for the full list of supported options.
|
34
|
+
#
|
35
|
+
# The interesting options are:
|
36
|
+
# * --daemon
|
37
|
+
# * --mailq
|
38
|
+
|
39
|
+
module ActionMailer; end
|
40
|
+
|
41
|
+
class ActionMailer::ARSendmail
|
42
|
+
|
43
|
+
##
|
44
|
+
# The version of ActionMailer::ARSendmail you are running.
|
45
|
+
|
46
|
+
VERSION = '2.1.5'
|
47
|
+
|
48
|
+
##
|
49
|
+
# Maximum number of times authentication will be consecutively retried
|
50
|
+
|
51
|
+
MAX_AUTH_FAILURES = 2
|
52
|
+
|
53
|
+
##
|
54
|
+
# Email delivery attempts per run
|
55
|
+
|
56
|
+
attr_accessor :batch_size
|
57
|
+
|
58
|
+
##
|
59
|
+
# Seconds to delay between runs
|
60
|
+
|
61
|
+
attr_accessor :delay
|
62
|
+
|
63
|
+
##
|
64
|
+
# Maximum age of emails in seconds before they are removed from the queue.
|
65
|
+
|
66
|
+
attr_accessor :max_age
|
67
|
+
|
68
|
+
##
|
69
|
+
# Be verbose
|
70
|
+
|
71
|
+
attr_accessor :verbose
|
72
|
+
|
73
|
+
|
74
|
+
##
|
75
|
+
# True if only one delivery attempt will be made per call to run
|
76
|
+
|
77
|
+
attr_reader :once
|
78
|
+
|
79
|
+
##
|
80
|
+
# Times authentication has failed
|
81
|
+
|
82
|
+
attr_accessor :failed_auth_count
|
83
|
+
|
84
|
+
@@pid_file = nil
|
85
|
+
|
86
|
+
def self.remove_pid_file
|
87
|
+
if @@pid_file
|
88
|
+
require 'shell'
|
89
|
+
sh = Shell.new
|
90
|
+
sh.rm @@pid_file
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Prints a list of unsent emails and the last delivery attempt, if any.
|
96
|
+
#
|
97
|
+
# If ActiveRecord::Timestamp is not being used the arrival time will not be
|
98
|
+
# known. See http://api.rubyonrails.org/classes/ActiveRecord/Timestamp.html
|
99
|
+
# to learn how to enable ActiveRecord::Timestamp.
|
100
|
+
|
101
|
+
def self.mailq
|
102
|
+
emails = ActionMailer::Base.email_class.find :all
|
103
|
+
|
104
|
+
if emails.empty? then
|
105
|
+
puts "Mail queue is empty"
|
106
|
+
return
|
107
|
+
end
|
108
|
+
|
109
|
+
total_size = 0
|
110
|
+
|
111
|
+
puts "-Queue ID- --Size-- ----Arrival Time---- -Sender/Recipient-------"
|
112
|
+
emails.each do |email|
|
113
|
+
size = email.mail.length
|
114
|
+
total_size += size
|
115
|
+
|
116
|
+
create_timestamp = email.created_on rescue
|
117
|
+
email.created_at rescue
|
118
|
+
Time.at(email.created_date) rescue # for Robot Co-op
|
119
|
+
nil
|
120
|
+
|
121
|
+
created = if create_timestamp.nil? then
|
122
|
+
' Unknown'
|
123
|
+
else
|
124
|
+
create_timestamp.strftime '%a %b %d %H:%M:%S'
|
125
|
+
end
|
126
|
+
|
127
|
+
puts "%10d %8d %s %s" % [email.id, size, created, email.from]
|
128
|
+
if email.last_send_attempt > 0 then
|
129
|
+
puts "Last send attempt: #{Time.at email.last_send_attempt}"
|
130
|
+
end
|
131
|
+
puts " #{email.to}"
|
132
|
+
puts
|
133
|
+
end
|
134
|
+
|
135
|
+
puts "-- #{total_size/1024} Kbytes in #{emails.length} Requests."
|
136
|
+
end
|
137
|
+
|
138
|
+
##
|
139
|
+
# Processes command line options in +args+
|
140
|
+
|
141
|
+
def self.process_args(args)
|
142
|
+
name = File.basename $0
|
143
|
+
|
144
|
+
options = {}
|
145
|
+
options[:Chdir] = '.'
|
146
|
+
options[:Daemon] = false
|
147
|
+
options[:Delay] = 60
|
148
|
+
options[:MaxAge] = 86400 * 7
|
149
|
+
options[:Once] = false
|
150
|
+
options[:RailsEnv] = ENV['RAILS_ENV']
|
151
|
+
options[:Pidfile] = options[:Chdir] + '/log/ar_sendmail.pid'
|
152
|
+
|
153
|
+
opts = OptionParser.new do |opts|
|
154
|
+
opts.banner = "Usage: #{name} [options]"
|
155
|
+
opts.separator ''
|
156
|
+
|
157
|
+
opts.separator "#{name} scans the email table for new messages and sends them to the"
|
158
|
+
opts.separator "website's configured SMTP host."
|
159
|
+
opts.separator ''
|
160
|
+
opts.separator "#{name} must be run from a Rails application's root."
|
161
|
+
|
162
|
+
opts.separator ''
|
163
|
+
opts.separator 'Sendmail options:'
|
164
|
+
|
165
|
+
opts.on("-b", "--batch-size BATCH_SIZE",
|
166
|
+
"Maximum number of emails to send per delay",
|
167
|
+
"Default: Deliver all available emails", Integer) do |batch_size|
|
168
|
+
options[:BatchSize] = batch_size
|
169
|
+
end
|
170
|
+
|
171
|
+
opts.on( "--delay DELAY",
|
172
|
+
"Delay between checks for new mail",
|
173
|
+
"in the database",
|
174
|
+
"Default: #{options[:Delay]}", Integer) do |delay|
|
175
|
+
options[:Delay] = delay
|
176
|
+
end
|
177
|
+
|
178
|
+
opts.on( "--max-age MAX_AGE",
|
179
|
+
"Maxmimum age for an email. After this",
|
180
|
+
"it will be removed from the queue.",
|
181
|
+
"Set to 0 to disable queue cleanup.",
|
182
|
+
"Default: #{options[:MaxAge]} seconds", Integer) do |max_age|
|
183
|
+
options[:MaxAge] = max_age
|
184
|
+
end
|
185
|
+
|
186
|
+
opts.on("-o", "--once",
|
187
|
+
"Only check for new mail and deliver once",
|
188
|
+
"Default: #{options[:Once]}") do |once|
|
189
|
+
options[:Once] = once
|
190
|
+
end
|
191
|
+
|
192
|
+
opts.on("-d", "--daemonize",
|
193
|
+
"Run as a daemon process",
|
194
|
+
"Default: #{options[:Daemon]}") do |daemon|
|
195
|
+
options[:Daemon] = true
|
196
|
+
end
|
197
|
+
|
198
|
+
opts.on("-p", "--pidfile PIDFILE",
|
199
|
+
"Set the pidfile location",
|
200
|
+
"Default: #{options[:Chdir]}#{options[:Pidfile]}", String) do |pidfile|
|
201
|
+
options[:Pidfile] = pidfile
|
202
|
+
end
|
203
|
+
|
204
|
+
opts.on( "--mailq",
|
205
|
+
"Display a list of emails waiting to be sent") do |mailq|
|
206
|
+
options[:MailQ] = true
|
207
|
+
end
|
208
|
+
|
209
|
+
opts.separator ''
|
210
|
+
opts.separator 'Setup Options:'
|
211
|
+
|
212
|
+
opts.separator ''
|
213
|
+
opts.separator 'Generic Options:'
|
214
|
+
|
215
|
+
opts.on("-c", "--chdir PATH",
|
216
|
+
"Use PATH for the application path",
|
217
|
+
"Default: #{options[:Chdir]}") do |path|
|
218
|
+
usage opts, "#{path} is not a directory" unless File.directory? path
|
219
|
+
usage opts, "#{path} is not readable" unless File.readable? path
|
220
|
+
options[:Chdir] = path
|
221
|
+
end
|
222
|
+
|
223
|
+
opts.on("-e", "--environment RAILS_ENV",
|
224
|
+
"Set the RAILS_ENV constant",
|
225
|
+
"Default: #{options[:RailsEnv]}") do |env|
|
226
|
+
options[:RailsEnv] = env
|
227
|
+
end
|
228
|
+
|
229
|
+
opts.on("-v", "--[no-]verbose",
|
230
|
+
"Be verbose",
|
231
|
+
"Default: #{options[:Verbose]}") do |verbose|
|
232
|
+
options[:Verbose] = verbose
|
233
|
+
end
|
234
|
+
|
235
|
+
opts.on("-h", "--help",
|
236
|
+
"You're looking at it") do
|
237
|
+
usage opts
|
238
|
+
end
|
239
|
+
|
240
|
+
opts.on("--version", "Version of ARMailer") do
|
241
|
+
usage "ar_mailer #{VERSION} (adzap fork)"
|
242
|
+
end
|
243
|
+
|
244
|
+
opts.separator ''
|
245
|
+
end
|
246
|
+
|
247
|
+
opts.parse! args
|
248
|
+
|
249
|
+
ENV['RAILS_ENV'] = options[:RailsEnv]
|
250
|
+
|
251
|
+
Dir.chdir options[:Chdir] do
|
252
|
+
begin
|
253
|
+
require 'config/environment'
|
254
|
+
require 'action_mailer/ar_mailer'
|
255
|
+
rescue LoadError
|
256
|
+
usage opts, <<-EOF
|
257
|
+
#{name} must be run from a Rails application's root to deliver email.
|
258
|
+
#{Dir.pwd} does not appear to be a Rails application root.
|
259
|
+
EOF
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
return options
|
264
|
+
end
|
265
|
+
|
266
|
+
##
|
267
|
+
# Processes +args+ and runs as appropriate
|
268
|
+
|
269
|
+
def self.run(args = ARGV)
|
270
|
+
options = process_args args
|
271
|
+
|
272
|
+
if options.include? :MailQ then
|
273
|
+
mailq
|
274
|
+
exit
|
275
|
+
end
|
276
|
+
|
277
|
+
if options[:Daemon] then
|
278
|
+
require 'webrick/server'
|
279
|
+
@@pid_file = File.expand_path(options[:Pidfile], options[:Chdir])
|
280
|
+
if File.exists? @@pid_file
|
281
|
+
# check to see if process is actually running
|
282
|
+
pid = ''
|
283
|
+
File.open(@@pid_file, 'r') {|f| pid = f.read.chomp }
|
284
|
+
if system("ps -p #{pid} | grep #{pid}") # returns true if process is running, o.w. false
|
285
|
+
$stderr.puts "Warning: The pid file #{@@pid_file} exists and ar_sendmail is running. Shutting down."
|
286
|
+
exit
|
287
|
+
else
|
288
|
+
# not running, so remove existing pid file and continue
|
289
|
+
self.remove_pid_file
|
290
|
+
$stderr.puts "ar_sendmail is not running. Removing existing pid file and starting up..."
|
291
|
+
end
|
292
|
+
end
|
293
|
+
WEBrick::Daemon.start
|
294
|
+
File.open(@@pid_file, 'w') {|f| f.write("#{Process.pid}\n")}
|
295
|
+
end
|
296
|
+
|
297
|
+
new(options).run
|
298
|
+
|
299
|
+
rescue SystemExit
|
300
|
+
raise
|
301
|
+
rescue SignalException
|
302
|
+
exit
|
303
|
+
rescue Exception => e
|
304
|
+
$stderr.puts "Unhandled exception #{e.message}(#{e.class}):"
|
305
|
+
$stderr.puts "\t#{e.backtrace.join "\n\t"}"
|
306
|
+
exit 1
|
307
|
+
end
|
308
|
+
|
309
|
+
##
|
310
|
+
# Prints a usage message to $stderr using +opts+ and exits
|
311
|
+
|
312
|
+
def self.usage(opts, message = nil)
|
313
|
+
if message then
|
314
|
+
$stderr.puts message
|
315
|
+
$stderr.puts
|
316
|
+
end
|
317
|
+
|
318
|
+
$stderr.puts opts
|
319
|
+
exit 1
|
320
|
+
end
|
321
|
+
|
322
|
+
##
|
323
|
+
# Creates a new ARSendmail.
|
324
|
+
#
|
325
|
+
# Valid options are:
|
326
|
+
# <tt>:BatchSize</tt>:: Maximum number of emails to send per delay
|
327
|
+
# <tt>:Delay</tt>:: Delay between deliver attempts
|
328
|
+
# <tt>:Once</tt>:: Only attempt to deliver emails once when run is called
|
329
|
+
# <tt>:Verbose</tt>:: Be verbose.
|
330
|
+
|
331
|
+
def initialize(options = {})
|
332
|
+
options[:Delay] ||= 60
|
333
|
+
options[:MaxAge] ||= 86400 * 7
|
334
|
+
|
335
|
+
@batch_size = options[:BatchSize]
|
336
|
+
@delay = options[:Delay]
|
337
|
+
@once = options[:Once]
|
338
|
+
@verbose = options[:Verbose]
|
339
|
+
@max_age = options[:MaxAge]
|
340
|
+
@smtp_settings = options[:smtp_settings]
|
341
|
+
|
342
|
+
@failed_auth_count = 0
|
343
|
+
end
|
344
|
+
|
345
|
+
##
|
346
|
+
# Removes emails that have lived in the queue for too long. If max_age is
|
347
|
+
# set to 0, no emails will be removed.
|
348
|
+
|
349
|
+
def cleanup
|
350
|
+
return if @max_age == 0
|
351
|
+
timeout = Time.now - @max_age
|
352
|
+
conditions = ['last_send_attempt > 0 and created_on < ?', timeout]
|
353
|
+
mail = ActionMailer::Base.email_class.destroy_all conditions
|
354
|
+
|
355
|
+
log "expired #{mail.length} emails from the queue"
|
356
|
+
end
|
357
|
+
|
358
|
+
##
|
359
|
+
# Delivers +emails+ to ActionMailer's SMTP server and destroys them.
|
360
|
+
|
361
|
+
def deliver(emails)
|
362
|
+
settings = [
|
363
|
+
smtp_settings[:domain],
|
364
|
+
(smtp_settings[:user] || smtp_settings[:user_name]),
|
365
|
+
smtp_settings[:password],
|
366
|
+
smtp_settings[:authentication]
|
367
|
+
]
|
368
|
+
|
369
|
+
smtp = Net::SMTP.new(smtp_settings[:address], smtp_settings[:port])
|
370
|
+
if smtp.respond_to?(:enable_starttls_auto)
|
371
|
+
smtp.enable_starttls_auto unless smtp_settings[:tls] == false
|
372
|
+
else
|
373
|
+
settings << smtp_settings[:tls]
|
374
|
+
end
|
375
|
+
|
376
|
+
smtp.start(*settings) do |session|
|
377
|
+
@failed_auth_count = 0
|
378
|
+
until emails.empty? do
|
379
|
+
email = emails.shift
|
380
|
+
begin
|
381
|
+
res = session.send_message email.mail, email.from, email.to
|
382
|
+
email.destroy
|
383
|
+
log "sent email %011d from %s to %s: %p" %
|
384
|
+
[email.id, email.from, email.to, res]
|
385
|
+
rescue Net::SMTPFatalError => e
|
386
|
+
log "5xx error sending email %d, removing from queue: %p(%s):\n\t%s" %
|
387
|
+
[email.id, e.message, e.class, e.backtrace.join("\n\t")]
|
388
|
+
email.destroy
|
389
|
+
session.reset
|
390
|
+
rescue Net::SMTPServerBusy => e
|
391
|
+
log "server too busy, sleeping #{@delay} seconds"
|
392
|
+
sleep delay
|
393
|
+
return
|
394
|
+
rescue Net::SMTPUnknownError, Net::SMTPSyntaxError, TimeoutError => e
|
395
|
+
email.last_send_attempt = Time.now.to_i
|
396
|
+
email.save rescue nil
|
397
|
+
log "error sending email %d: %p(%s):\n\t%s" %
|
398
|
+
[email.id, e.message, e.class, e.backtrace.join("\n\t")]
|
399
|
+
session.reset
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
403
|
+
rescue Net::SMTPAuthenticationError => e
|
404
|
+
@failed_auth_count += 1
|
405
|
+
if @failed_auth_count >= MAX_AUTH_FAILURES then
|
406
|
+
log "authentication error, giving up: #{e.message}"
|
407
|
+
raise e
|
408
|
+
else
|
409
|
+
log "authentication error, retrying: #{e.message}"
|
410
|
+
end
|
411
|
+
sleep delay
|
412
|
+
rescue Net::SMTPServerBusy, SystemCallError, OpenSSL::SSL::SSLError
|
413
|
+
# ignore SMTPServerBusy/EPIPE/ECONNRESET from Net::SMTP.start's ensure
|
414
|
+
end
|
415
|
+
|
416
|
+
##
|
417
|
+
# Prepares ar_sendmail for exiting
|
418
|
+
|
419
|
+
def do_exit
|
420
|
+
log "caught signal, shutting down"
|
421
|
+
self.class.remove_pid_file
|
422
|
+
exit
|
423
|
+
end
|
424
|
+
|
425
|
+
##
|
426
|
+
# Returns emails in email_class that haven't had a delivery attempt in the
|
427
|
+
# last 300 seconds.
|
428
|
+
|
429
|
+
def find_emails
|
430
|
+
options = { :conditions => ['last_send_attempt < ? and ready', Time.now.to_i - 300] }
|
431
|
+
options[:limit] = batch_size unless batch_size.nil?
|
432
|
+
mail = ActionMailer::Base.email_class.find :all, options
|
433
|
+
|
434
|
+
log "found #{mail.length} emails to send"
|
435
|
+
mail
|
436
|
+
end
|
437
|
+
|
438
|
+
##
|
439
|
+
# Installs signal handlers to gracefully exit.
|
440
|
+
|
441
|
+
def install_signal_handlers
|
442
|
+
trap 'TERM' do do_exit end
|
443
|
+
trap 'INT' do do_exit end
|
444
|
+
end
|
445
|
+
|
446
|
+
##
|
447
|
+
# Logs +message+ if verbose
|
448
|
+
|
449
|
+
def log(message)
|
450
|
+
$stderr.puts message if @verbose
|
451
|
+
ActionMailer::Base.logger.info "ar_sendmail: #{message}"
|
452
|
+
end
|
453
|
+
|
454
|
+
##
|
455
|
+
# Scans for emails and delivers them every delay seconds. Only returns if
|
456
|
+
# once is true.
|
457
|
+
|
458
|
+
def run
|
459
|
+
install_signal_handlers
|
460
|
+
|
461
|
+
loop do
|
462
|
+
now = Time.now
|
463
|
+
deliver_emails
|
464
|
+
break if @once
|
465
|
+
sleep @delay if now + @delay > Time.now
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
def deliver_emails
|
470
|
+
begin
|
471
|
+
cleanup
|
472
|
+
emails = find_emails
|
473
|
+
deliver(emails) unless emails.empty?
|
474
|
+
rescue ActiveRecord::Transactions::TransactionError
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
##
|
479
|
+
# Proxy to ActionMailer::Base::smtp_settings. See
|
480
|
+
# http://api.rubyonrails.org/classes/ActionMailer/Base.html
|
481
|
+
# for instructions on how to configure ActionMailer's SMTP server.
|
482
|
+
#
|
483
|
+
# Falls back to ::server_settings if ::smtp_settings doesn't exist for
|
484
|
+
# backwards compatibility.
|
485
|
+
|
486
|
+
def smtp_settings
|
487
|
+
@smtp_settings ||= ActionMailer::Base.smtp_settings rescue ActionMailer::Base.server_settings
|
488
|
+
end
|
489
|
+
|
490
|
+
##
|
491
|
+
# Packs non-digested messages into digests, and send them
|
492
|
+
# options: (see default values in first lines of code)
|
493
|
+
# :dump_path - dir in which to dump full digest message in case it needs to be truncated.
|
494
|
+
# :subj_prefix - specifies subject prefix that is already set to all the messages. If it's specified it's removed from subjects when combining them into digest's subject.
|
495
|
+
# :max_subj_size - maximum subject size of digest email
|
496
|
+
# :mail_body_size_limit - maximum body size of digest email
|
497
|
+
# :smtp_settings - what smtp settings to use. Hash is expected described in http://api.rubyonrails.org/classes/ActionMailer/Base.html, +setting :tls to false will work. If omitted asks it from #smtp_settings method
|
498
|
+
def self.digest_error_emails(options = {})
|
499
|
+
options.reverse_merge! :dump_path => 'log', :max_subj_size => 150, :mail_body_size_limit => 1.megabyte
|
500
|
+
|
501
|
+
subj_prefix = options[:subj_prefix]
|
502
|
+
max_subj_size = options[:max_subj_size]
|
503
|
+
mail_body_size_limit = options[:mail_body_size_limit]
|
504
|
+
|
505
|
+
email_class = ActionMailer::Base.email_class
|
506
|
+
#Email.count(:group => :to) doesn't work because to is reserved word in MySQL
|
507
|
+
counts = email_class.connection.select_rows("select `to`, count(*) AS count_all FROM `emails` where !ready GROUP BY `to`")
|
508
|
+
counts.each do |to, count|
|
509
|
+
if count.to_i == 1
|
510
|
+
email_class.update_all(['ready = ?', true], ['`to` = ?', to])
|
511
|
+
next
|
512
|
+
end
|
513
|
+
subjects = []
|
514
|
+
mails = []
|
515
|
+
from = nil
|
516
|
+
email_class.transaction do
|
517
|
+
emails = email_class.find(:all, :conditions => {:to => to, :ready => false}, :order => 'created_on')
|
518
|
+
msg = nil
|
519
|
+
email = nil
|
520
|
+
last_date = nil
|
521
|
+
emails.each do |email|
|
522
|
+
msg = TMail::Mail.parse(email.mail)
|
523
|
+
subject = msg.subject
|
524
|
+
subject = subject[subj_prefix.length..-1] if subject.starts_with?(subj_prefix)
|
525
|
+
subjects << subject
|
526
|
+
mail = msg.header.select {|key, value| ['date', 'from', 'subject'].include?(key)}.
|
527
|
+
map {|key, value| '%s: %s' % [key.capitalize, value.to_s]}.join("\n")
|
528
|
+
mail += "\n\n" + msg.body
|
529
|
+
mails << mail
|
530
|
+
from = msg.header['from'].to_s
|
531
|
+
last_date = msg.date
|
532
|
+
email.destroy
|
533
|
+
end
|
534
|
+
new = TMail::Mail.new
|
535
|
+
new.to = to
|
536
|
+
new.from = from
|
537
|
+
subject = subj_prefix + subjects.uniq.join("; ")
|
538
|
+
new.subject = subject.size > max_subj_size ? subject[0..max_subj_size] + '... (and more)' : subject
|
539
|
+
new.mime_version = msg.mime_version
|
540
|
+
new.content_type = "text/plain" #this code doesn't really support anything else
|
541
|
+
new.charset = msg.charset
|
542
|
+
new.date = last_date
|
543
|
+
splitter = "\n" + '=' * 70 + "\n"
|
544
|
+
body = splitter + mails.join(splitter)
|
545
|
+
if body.size > mail_body_size_limit
|
546
|
+
email_dump_path = options[:dump_path] + "/err.emails.#{Time.now.to_i}"
|
547
|
+
File.open(email_dump_path, 'w') do |f|
|
548
|
+
f.write(body)
|
549
|
+
end
|
550
|
+
old_size = body.size
|
551
|
+
body = body[0..mail_body_size_limit] #yeah it will be bit more considering the header, but we don't care
|
552
|
+
new_num = body.split(splitter).size
|
553
|
+
body = ("WARNING: not all the messages made it into this digest - some are lost in truncation. " +
|
554
|
+
"Original number of messages - #{mails.size} (here only #{new_num}); original size - #{old_size} " +
|
555
|
+
"(here only #{body.size}). Full dump of original emails is placed in #{email_dump_path} @#{`hostname`.strip}.\n\n") + body
|
556
|
+
end
|
557
|
+
new.body = body
|
558
|
+
email = email_class.create!(:from => email.from, :to => to, :mail => new.to_s, :ready => true)
|
559
|
+
end
|
560
|
+
end
|
561
|
+
self.new(:smtp_settings => options[:smtp_settings]).deliver_emails
|
562
|
+
end
|
563
|
+
end
|
data/lib/smtp_tls.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
# Original code believed public domain from ruby-talk or ruby-core email.
|
2
|
+
# Modifications by Kyle Maxwell <kyle@kylemaxwell.com> used under MIT license.
|
3
|
+
|
4
|
+
require "openssl"
|
5
|
+
require "net/smtp"
|
6
|
+
|
7
|
+
# :stopdoc:
|
8
|
+
|
9
|
+
class Net::SMTP
|
10
|
+
|
11
|
+
class << self
|
12
|
+
send :remove_method, :start
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.start( address, port = nil,
|
16
|
+
helo = 'localhost.localdomain',
|
17
|
+
user = nil, secret = nil, authtype = nil, use_tls = false,
|
18
|
+
&block) # :yield: smtp
|
19
|
+
new(address, port).start(helo, user, secret, authtype, use_tls, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
alias tls_old_start start
|
23
|
+
|
24
|
+
def start( helo = 'localhost.localdomain',
|
25
|
+
user = nil, secret = nil, authtype = nil, use_tls = false ) # :yield: smtp
|
26
|
+
start_method = use_tls ? :do_tls_start : :do_start
|
27
|
+
if block_given?
|
28
|
+
begin
|
29
|
+
send start_method, helo, user, secret, authtype
|
30
|
+
return yield(self)
|
31
|
+
ensure
|
32
|
+
do_finish
|
33
|
+
end
|
34
|
+
else
|
35
|
+
send start_method, helo, user, secret, authtype
|
36
|
+
return self
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def do_tls_start(helodomain, user, secret, authtype)
|
43
|
+
raise IOError, 'SMTP session already started' if @started
|
44
|
+
check_auth_args user, secret, authtype if user or secret
|
45
|
+
|
46
|
+
sock = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
|
47
|
+
@socket = Net::InternetMessageIO.new(sock)
|
48
|
+
@socket.read_timeout = 60 #@read_timeout
|
49
|
+
@socket.debug_output = STDERR #@debug_output
|
50
|
+
|
51
|
+
check_response(critical { recv_response() })
|
52
|
+
do_helo(helodomain)
|
53
|
+
|
54
|
+
raise 'openssl library not installed' unless defined?(OpenSSL)
|
55
|
+
starttls
|
56
|
+
ssl = OpenSSL::SSL::SSLSocket.new(sock)
|
57
|
+
ssl.sync_close = true
|
58
|
+
ssl.connect
|
59
|
+
@socket = Net::InternetMessageIO.new(ssl)
|
60
|
+
@socket.read_timeout = 60 #@read_timeout
|
61
|
+
@socket.debug_output = STDERR #@debug_output
|
62
|
+
do_helo(helodomain)
|
63
|
+
|
64
|
+
authenticate user, secret, authtype if user
|
65
|
+
@started = true
|
66
|
+
ensure
|
67
|
+
unless @started
|
68
|
+
# authentication failed, cancel connection.
|
69
|
+
@socket.close if not @started and @socket and not @socket.closed?
|
70
|
+
@socket = nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def do_helo(helodomain)
|
75
|
+
begin
|
76
|
+
if @esmtp
|
77
|
+
ehlo helodomain
|
78
|
+
else
|
79
|
+
helo helodomain
|
80
|
+
end
|
81
|
+
rescue Net::ProtocolError
|
82
|
+
if @esmtp
|
83
|
+
@esmtp = false
|
84
|
+
@error_occured = false
|
85
|
+
retry
|
86
|
+
end
|
87
|
+
raise
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def starttls
|
92
|
+
getok('STARTTLS')
|
93
|
+
end
|
94
|
+
|
95
|
+
alias tls_old_quit quit
|
96
|
+
|
97
|
+
def quit
|
98
|
+
begin
|
99
|
+
getok('QUIT')
|
100
|
+
rescue EOFError
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end unless Net::SMTP.private_method_defined? :do_tls_start or
|
105
|
+
Net::SMTP.method_defined? :tls?
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
# PROVIDE: ar_sendmail
|
3
|
+
# REQUIRE: DAEMON
|
4
|
+
# BEFORE: LOGIN
|
5
|
+
# KEYWORD: FreeBSD shutdown
|
6
|
+
|
7
|
+
#
|
8
|
+
# Add the following lines to /etc/rc.conf to enable ar_sendmail:
|
9
|
+
#
|
10
|
+
#ar_sendmail_enable="YES"
|
11
|
+
|
12
|
+
. /etc/rc.subr
|
13
|
+
|
14
|
+
name="ar_sendmail"
|
15
|
+
rcvar=`set_rcvar`
|
16
|
+
|
17
|
+
command="/usr/local/bin/ar_sendmail"
|
18
|
+
command_interpreter="/usr/local/bin/ruby18"
|
19
|
+
|
20
|
+
# set defaults
|
21
|
+
|
22
|
+
ar_sendmail_rails_env=${ar_sendmail_rails_env:-"production"}
|
23
|
+
ar_sendmail_chdir=${ar_sendmail_chdir:-"/"}
|
24
|
+
ar_sendmail_enable=${ar_sendmail_enable:-"NO"}
|
25
|
+
ar_sendmail_flags=${ar_sendmail_flags:-"-d"}
|
26
|
+
|
27
|
+
load_rc_config $name
|
28
|
+
export RAILS_ENV=$ar_sendmail_rails_env
|
29
|
+
run_rc_command "$1"
|
30
|
+
|