mailhandler 1.0.38 → 1.0.39
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.
- checksums.yaml +5 -5
- data/.rubocop.yml +26 -0
- data/Gemfile +2 -0
- data/lib/mailhandler.rb +2 -10
- data/lib/mailhandler/errors.rb +1 -0
- data/lib/mailhandler/receiver.rb +14 -6
- data/lib/mailhandler/receiving/base.rb +28 -22
- data/lib/mailhandler/receiving/filelist/base.rb +17 -6
- data/lib/mailhandler/receiving/filelist/filter/base.rb +8 -3
- data/lib/mailhandler/receiving/filelist/filter/email.rb +3 -0
- data/lib/mailhandler/receiving/folder.rb +8 -3
- data/lib/mailhandler/receiving/imap.rb +53 -51
- data/lib/mailhandler/receiving/mail.rb +6 -13
- data/lib/mailhandler/receiving/notification/console.rb +2 -1
- data/lib/mailhandler/receiving/notification/email.rb +7 -4
- data/lib/mailhandler/receiving/notification/email/content.rb +1 -4
- data/lib/mailhandler/receiving/notification/email/states.rb +23 -20
- data/lib/mailhandler/receiving/observer.rb +2 -1
- data/lib/mailhandler/sender.rb +1 -1
- data/lib/mailhandler/sending/api.rb +3 -3
- data/lib/mailhandler/sending/api_batch.rb +4 -1
- data/lib/mailhandler/sending/base.rb +4 -1
- data/lib/mailhandler/sending/smtp.rb +0 -1
- data/lib/mailhandler/version.rb +1 -1
- data/mailhandler.gemspec +1 -2
- data/spec/unit/mailhandler/receiver_spec.rb +23 -15
- data/spec/unit/mailhandler/receiving/base_spec.rb +8 -7
- data/spec/unit/mailhandler/receiving/folder_spec.rb +42 -29
- data/spec/unit/mailhandler/receiving/imap_spec.rb +4 -5
- data/spec/unit/mailhandler/receiving/notification/console_spec.rb +5 -5
- data/spec/unit/mailhandler/receiving/notification/email/content_spec.rb +22 -18
- data/spec/unit/mailhandler/receiving/notification/email_spec.rb +12 -12
- data/spec/unit/mailhandler/sender_spec.rb +7 -3
- data/spec/unit/mailhandler/sending/sender_api_batch_spec.rb +9 -7
- data/spec/unit/mailhandler/sending/sender_api_spec.rb +4 -4
- data/spec/unit/mailhandler/sending/sender_smtp_spec.rb +7 -7
- data/spec/unit/mailhandler_spec.rb +10 -9
- metadata +6 -6
@@ -1,8 +1,9 @@
|
|
1
1
|
module Mail
|
2
|
+
# IMAP class patch to better manage connection and search of emails
|
2
3
|
class IMAP
|
3
4
|
attr_accessor :imap_connection
|
4
5
|
|
5
|
-
def find_emails(options = {}, &block)
|
6
|
+
def find_emails(options = {}, &block) # rubocop:disable all
|
6
7
|
options = validate_options(options)
|
7
8
|
options[:read_only] ? imap_connection.examine(options[:mailbox]) : imap_connection.select(options[:mailbox])
|
8
9
|
uids = imap_connection.uid_search(options[:keys])
|
@@ -13,7 +14,6 @@ module Mail
|
|
13
14
|
(options[:what].to_sym != :last && options[:order].to_sym == :desc)
|
14
15
|
|
15
16
|
if block_given?
|
16
|
-
|
17
17
|
uids.each do |uid|
|
18
18
|
uid = options[:uid].to_i unless options[:uid].nil?
|
19
19
|
fetchdata = imap_connection.uid_fetch(uid, ['RFC822'])[0]
|
@@ -26,14 +26,12 @@ module Mail
|
|
26
26
|
yield new_message
|
27
27
|
end
|
28
28
|
|
29
|
-
imap_connection.uid_store(uid, '+FLAGS', [Net::IMAP::DELETED]) if options[:delete_after_find] &&
|
29
|
+
imap_connection.uid_store(uid, '+FLAGS', [Net::IMAP::DELETED]) if options[:delete_after_find] &&
|
30
|
+
new_message.is_marked_for_delete?
|
30
31
|
break unless options[:uid].nil?
|
31
32
|
end
|
32
|
-
|
33
33
|
imap_connection.expunge if options[:delete_after_find]
|
34
|
-
|
35
34
|
else
|
36
|
-
|
37
35
|
emails = []
|
38
36
|
|
39
37
|
uids.each do |uid|
|
@@ -46,12 +44,11 @@ module Mail
|
|
46
44
|
|
47
45
|
imap_connection.expunge if options[:delete_after_find]
|
48
46
|
emails.size == 1 && options[:count] == 1 ? emails.first : emails
|
49
|
-
|
50
47
|
end
|
51
48
|
end
|
52
49
|
|
53
50
|
# Start an IMAP session
|
54
|
-
def connect(_config = Mail::Configuration.instance)
|
51
|
+
def connect(_config = Mail::Configuration.instance) # rubocop:disable all
|
55
52
|
@imap_connection = Net::IMAP.new(settings[:address], settings[:port], settings[:enable_ssl], nil, false)
|
56
53
|
|
57
54
|
if settings[:authentication].nil?
|
@@ -64,11 +61,7 @@ module Mail
|
|
64
61
|
end
|
65
62
|
|
66
63
|
def disconnect
|
67
|
-
if defined?(imap_connection) && imap_connection && !imap_connection.disconnected?
|
68
|
-
|
69
|
-
imap_connection.disconnect
|
70
|
-
|
71
|
-
end
|
64
|
+
imap_connection.disconnect if defined?(imap_connection) && imap_connection && !imap_connection.disconnected?
|
72
65
|
end
|
73
66
|
end
|
74
67
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module MailHandler
|
2
2
|
module Receiving
|
3
3
|
module Notification
|
4
|
+
# notification in form of console output
|
4
5
|
class Console
|
5
6
|
def notify(search)
|
6
7
|
output_delay Time.now - search.started_at
|
@@ -15,7 +16,7 @@ module MailHandler
|
|
15
16
|
# print to screen delay length
|
16
17
|
def output_delay(delay)
|
17
18
|
delay_seconds = delay.to_i
|
18
|
-
output(delay_seconds) if [0, 1].include?
|
19
|
+
output(delay_seconds) if [0, 1].include?(delay_seconds % Seconds::TO_SHOW)
|
19
20
|
end
|
20
21
|
|
21
22
|
# print to screen delay length
|
@@ -5,6 +5,7 @@ require_relative '../../errors'
|
|
5
5
|
module MailHandler
|
6
6
|
module Receiving
|
7
7
|
module Notification
|
8
|
+
# notification in form of sent email
|
8
9
|
class Email
|
9
10
|
attr_accessor :sender,
|
10
11
|
:from,
|
@@ -21,7 +22,7 @@ module MailHandler
|
|
21
22
|
@from = from
|
22
23
|
@contacts = to
|
23
24
|
init_state
|
24
|
-
|
25
|
+
change_content_handler(EmailContent.new)
|
25
26
|
end
|
26
27
|
|
27
28
|
def notify(search)
|
@@ -30,7 +31,7 @@ module MailHandler
|
|
30
31
|
@current_state.notify(search)
|
31
32
|
end
|
32
33
|
|
33
|
-
def
|
34
|
+
def change_state(state)
|
34
35
|
@current_state = state
|
35
36
|
end
|
36
37
|
|
@@ -42,7 +43,7 @@ module MailHandler
|
|
42
43
|
|
43
44
|
# Allow users to specify their own content classes.
|
44
45
|
# Class must match by methods to the interface of MailHandler::Receiving::Notification::EmailContent
|
45
|
-
def
|
46
|
+
def change_content_handler(content_handler)
|
46
47
|
@content_handler = content_handler
|
47
48
|
end
|
48
49
|
|
@@ -55,7 +56,9 @@ module MailHandler
|
|
55
56
|
EMAIL_TYPES = %i[delayed received].freeze
|
56
57
|
|
57
58
|
def verify_email_type(type)
|
58
|
-
|
59
|
+
return if EMAIL_TYPES.include?(type)
|
60
|
+
|
61
|
+
raise MailHandler::TypeError, "Incorrect type: #{type}, allowed types: #{EMAIL_TYPES}."
|
59
62
|
end
|
60
63
|
end
|
61
64
|
end
|
@@ -3,6 +3,7 @@ require 'mail'
|
|
3
3
|
module MailHandler
|
4
4
|
module Receiving
|
5
5
|
module Notification
|
6
|
+
# email notification sent
|
6
7
|
class EmailContent
|
7
8
|
# @param [Symbol] type - notification type
|
8
9
|
# @param [Hash] options - search options used for searching for an email
|
@@ -16,19 +17,15 @@ module MailHandler
|
|
16
17
|
delay = (delay.to_f / 60).round(2)
|
17
18
|
|
18
19
|
case type
|
19
|
-
|
20
20
|
when :received
|
21
|
-
|
22
21
|
mail.subject = "Received - delay was #{delay} minutes"
|
23
22
|
mail.body = "Received - delay was #{delay} minutes - search by #{options}"
|
24
23
|
|
25
24
|
when :delayed
|
26
|
-
|
27
25
|
mail.subject = "Over #{delay} minutes delay"
|
28
26
|
mail.body = "Over #{delay} minutes delay - search by #{options}"
|
29
27
|
|
30
28
|
else
|
31
|
-
|
32
29
|
raise StandardError, "Incorrect type: #{type}"
|
33
30
|
|
34
31
|
end
|
@@ -4,6 +4,7 @@ require_relative '../../../errors'
|
|
4
4
|
module MailHandler
|
5
5
|
module Receiving
|
6
6
|
module Notification
|
7
|
+
# base state
|
7
8
|
class DelayState
|
8
9
|
attr_accessor :context,
|
9
10
|
:notified
|
@@ -23,52 +24,54 @@ module MailHandler
|
|
23
24
|
protected
|
24
25
|
|
25
26
|
def send_notification_email(type, search)
|
26
|
-
|
27
|
+
return if notified
|
27
28
|
|
28
|
-
|
29
|
-
|
29
|
+
context.send_email(type, search)
|
30
|
+
notification_fired
|
31
|
+
end
|
30
32
|
|
31
|
-
|
33
|
+
def change_notification_state(search, state)
|
34
|
+
context.change_state(state)
|
35
|
+
context.notify(search)
|
32
36
|
end
|
33
37
|
end
|
34
38
|
|
39
|
+
# there was no delay
|
35
40
|
class NoDelay < DelayState
|
36
41
|
def notify(search)
|
37
|
-
|
42
|
+
return unless Time.now - search.started_at >= context.min_time_to_notify
|
38
43
|
|
39
|
-
|
40
|
-
context.notify(search)
|
41
|
-
|
42
|
-
end
|
44
|
+
change_notification_state(search, Delay.new(context))
|
43
45
|
end
|
44
46
|
end
|
45
47
|
|
48
|
+
# delay happened
|
46
49
|
class Delay < DelayState
|
47
50
|
def notify(search)
|
48
51
|
if search.result
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
elsif Time.now - search.started_at >= context.max_time_to_notify
|
54
|
-
|
55
|
-
context.set_state(MaxDelay.new(context))
|
56
|
-
context.notify(search)
|
57
|
-
|
52
|
+
change_notification_state(search, Received.new(context))
|
53
|
+
elsif max_time_to_notify?(search)
|
54
|
+
change_notification_state(search, MaxDelay.new(context))
|
58
55
|
else
|
59
|
-
|
60
56
|
send_notification_email(:delayed, search)
|
61
|
-
|
62
57
|
end
|
63
58
|
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def max_time_to_notify?(search)
|
63
|
+
Time.now - search.started_at >= context.max_time_to_notify
|
64
|
+
end
|
64
65
|
end
|
65
66
|
|
67
|
+
# maximum delay checked happened
|
66
68
|
class MaxDelay < DelayState
|
67
69
|
def notify(search)
|
68
70
|
send_notification_email(:delayed, search)
|
69
71
|
end
|
70
72
|
end
|
71
73
|
|
74
|
+
# no more delays will be fired
|
72
75
|
class Received < DelayState
|
73
76
|
def notify(search)
|
74
77
|
send_notification_email(:received, search)
|
data/lib/mailhandler/sender.rb
CHANGED
@@ -2,8 +2,8 @@ require_relative 'sending/smtp'
|
|
2
2
|
require_relative 'sending/api'
|
3
3
|
require_relative 'sending/api_batch'
|
4
4
|
|
5
|
-
# Class for sending email, and storing details about the sending.
|
6
5
|
module MailHandler
|
6
|
+
# Class for sending email, and storing details about the sending.
|
7
7
|
class Sender
|
8
8
|
attr_accessor :dispatcher,
|
9
9
|
:sending
|
@@ -4,6 +4,7 @@ require_relative 'base.rb'
|
|
4
4
|
|
5
5
|
module MailHandler
|
6
6
|
module Sending
|
7
|
+
# sending email by Postmark API
|
7
8
|
class PostmarkAPISender < Sender
|
8
9
|
attr_accessor :host,
|
9
10
|
:api_token,
|
@@ -35,11 +36,10 @@ module MailHandler
|
|
35
36
|
def setup_sending_client
|
36
37
|
# clearing cache so valid host is accepted, and not the cached one
|
37
38
|
Postmark::HttpClient.instance_variable_set('@http', nil)
|
38
|
-
Postmark::ApiClient.new(api_token, http_open_timeout: http_open_timeout, http_read_timeout: http_read_timeout,
|
39
|
+
Postmark::ApiClient.new(api_token, http_open_timeout: http_open_timeout, http_read_timeout: http_read_timeout,
|
40
|
+
host: host, secure: @use_ssl)
|
39
41
|
end
|
40
42
|
|
41
|
-
protected
|
42
|
-
|
43
43
|
DEFAULT_HOST = 'api.postmarkapp.com'.freeze
|
44
44
|
end
|
45
45
|
end
|
@@ -3,6 +3,7 @@ require_relative '../errors'
|
|
3
3
|
|
4
4
|
module MailHandler
|
5
5
|
module Sending
|
6
|
+
# sending batch email by Postmark API
|
6
7
|
class PostmarkBatchAPISender < PostmarkAPISender
|
7
8
|
def initialize(api_token = nil)
|
8
9
|
super(api_token)
|
@@ -17,7 +18,9 @@ module MailHandler
|
|
17
18
|
protected
|
18
19
|
|
19
20
|
def verify_email(emails)
|
20
|
-
|
21
|
+
return if emails.is_a?(Array) && emails.all? { |e| e.is_a? allowed_email_type }
|
22
|
+
|
23
|
+
raise MailHandler::TypeError, 'Invalid type error, only Array of Mail::Message object types for sending allowed'
|
21
24
|
end
|
22
25
|
end
|
23
26
|
end
|
@@ -2,6 +2,7 @@ require_relative '../errors'
|
|
2
2
|
|
3
3
|
module MailHandler
|
4
4
|
module Sending
|
5
|
+
# email sending handler class
|
5
6
|
class Sender
|
6
7
|
attr_reader :type
|
7
8
|
|
@@ -12,7 +13,9 @@ module MailHandler
|
|
12
13
|
protected
|
13
14
|
|
14
15
|
def verify_email(email)
|
15
|
-
|
16
|
+
return if email.is_a?(allowed_email_type)
|
17
|
+
|
18
|
+
raise MailHandler::TypeError, "Invalid type error, only #{allowed_email_type} object type for sending allowed."
|
16
19
|
end
|
17
20
|
|
18
21
|
private
|
data/lib/mailhandler/version.rb
CHANGED
data/mailhandler.gemspec
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
lib = File.expand_path('lib', __dir__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
3
|
require 'mailhandler/version'
|
@@ -13,7 +12,7 @@ Gem::Specification.new do |s|
|
|
13
12
|
s.email = ['ibalosh@gmail.com', 'igor@wildbit.com']
|
14
13
|
|
15
14
|
s.summary = 'Postmark email receiving and sending handler.'
|
16
|
-
s.description = 'Use this gem to send emails through SMTP and Postmark API
|
15
|
+
s.description = 'Use this gem to send emails through SMTP and Postmark API and check if email arrived.'
|
17
16
|
|
18
17
|
s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
19
18
|
s.test_files = `git ls-files -- {spec}/*`.split("\n")
|
@@ -2,21 +2,26 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe MailHandler::Receiver do
|
4
4
|
context 'valid receiver' do
|
5
|
+
subject(:receiver) { described_class.new(checker) }
|
6
|
+
|
5
7
|
let(:default_search_option) { { by_subject: 'test' } }
|
6
8
|
let(:receiving_duration) { 5 }
|
7
9
|
let(:found_email) { Mail.new { subject 'test email' } }
|
8
10
|
let(:checker) do
|
9
|
-
checker =
|
11
|
+
checker = instance_double('Checker')
|
12
|
+
|
13
|
+
allow(checker).to receive(:find) do
|
14
|
+
sleep receiving_duration
|
15
|
+
true
|
16
|
+
end
|
10
17
|
|
11
|
-
allow(checker).to receive(:
|
12
|
-
allow(checker).to receive(:search_result) { true }
|
18
|
+
allow(checker).to receive(:search_result).and_return(true)
|
13
19
|
allow(checker).to receive(:found_emails) { [found_email] }
|
14
|
-
allow(checker).to receive(:reset_found_emails)
|
15
|
-
allow(checker).to receive(:start)
|
16
|
-
allow(checker).to receive(:stop)
|
20
|
+
allow(checker).to receive(:reset_found_emails).and_return([])
|
21
|
+
allow(checker).to receive(:start).and_return(nil)
|
22
|
+
allow(checker).to receive(:stop).and_return(nil)
|
17
23
|
checker
|
18
24
|
end
|
19
|
-
subject(:receiver) { MailHandler::Receiver.new(checker) }
|
20
25
|
|
21
26
|
context 'att readers' do
|
22
27
|
it { is_expected.to respond_to(:checker) }
|
@@ -31,7 +36,7 @@ describe MailHandler::Receiver do
|
|
31
36
|
end
|
32
37
|
|
33
38
|
it 'create' do
|
34
|
-
|
39
|
+
expect(receiver).to be_kind_of described_class
|
35
40
|
end
|
36
41
|
|
37
42
|
it '.find_email' do
|
@@ -54,14 +59,17 @@ describe MailHandler::Receiver do
|
|
54
59
|
|
55
60
|
context '.search' do
|
56
61
|
let(:checker) do
|
57
|
-
checker =
|
62
|
+
checker = instance_double('Checker')
|
58
63
|
|
59
|
-
allow(checker).to receive(:find)
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
allow(checker).to receive(:
|
64
|
-
allow(checker).to receive(:
|
64
|
+
allow(checker).to receive(:find) do
|
65
|
+
sleep 1
|
66
|
+
false
|
67
|
+
end
|
68
|
+
allow(checker).to receive(:search_result).and_return(false)
|
69
|
+
allow(checker).to receive(:found_emails).and_return([])
|
70
|
+
allow(checker).to receive(:reset_found_emails).and_return([])
|
71
|
+
allow(checker).to receive(:start).and_return(nil)
|
72
|
+
allow(checker).to receive(:stop).and_return(nil)
|
65
73
|
checker
|
66
74
|
end
|
67
75
|
|
@@ -1,25 +1,26 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe MailHandler::Receiving::Checker do
|
4
|
-
subject {
|
4
|
+
subject(:receiving_checker) { described_class.new }
|
5
5
|
|
6
6
|
it '.create' do
|
7
|
-
|
7
|
+
expect(receiving_checker).to be_kind_of described_class
|
8
8
|
end
|
9
9
|
|
10
10
|
it 'init details' do
|
11
11
|
aggregate_failures 'receiving details' do
|
12
|
-
expect(
|
13
|
-
expect(
|
14
|
-
expect(
|
12
|
+
expect(receiving_checker.search_options).to eq(count: 50, archive: false)
|
13
|
+
expect(receiving_checker.found_emails).to eq []
|
14
|
+
expect(receiving_checker.search_result).to be false
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
18
|
it '.find' do
|
19
|
-
expect {
|
19
|
+
expect { receiving_checker.find(by_subject: 'test') }
|
20
|
+
.to raise_error(MailHandler::InterfaceError, 'Find interface not implemented.')
|
20
21
|
end
|
21
22
|
|
22
23
|
it '.search_result' do
|
23
|
-
expect(
|
24
|
+
expect(receiving_checker.search_result).to be false
|
24
25
|
end
|
25
26
|
end
|