mailhandler 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/lib/mailhandler.rb +91 -0
- data/lib/mailhandler/receiver.rb +86 -0
- data/lib/mailhandler/receiving/checker.rb +60 -0
- data/lib/mailhandler/receiving/checker_folder.rb +126 -0
- data/lib/mailhandler/receiving/checker_imap.rb +90 -0
- data/lib/mailhandler/receiving/filter.rb +161 -0
- data/lib/mailhandler/receiving/notification/console.rb +44 -0
- data/lib/mailhandler/receiving/notification/email.rb +74 -0
- data/lib/mailhandler/receiving/notification/email/content.rb +43 -0
- data/lib/mailhandler/receiving/notification/email/states.rb +113 -0
- data/lib/mailhandler/receiving/observer.rb +37 -0
- data/lib/mailhandler/sender.rb +56 -0
- data/lib/mailhandler/sending/sender.rb +13 -0
- data/lib/mailhandler/sending/sender_api.rb +45 -0
- data/lib/mailhandler/sending/sender_api_batch.rb +20 -0
- data/lib/mailhandler/sending/sender_smtp.rb +76 -0
- data/lib/mailhandler/version.rb +5 -0
- data/mailhandler.gemspec +28 -0
- data/readme.md +151 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ad4de21ce6ad278dd2916e5cb898691c10961b87
|
4
|
+
data.tar.gz: caedb45d153cbfa44d41b13328a9afb20c81b2fa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a0da923ce9b696ca05ce4cc1b322678bd272b44227ca25a330239375fac65c3bdddbe3fbc08b6b425a27b89c24907b4a48fd259a5a99cec88f5e4ac6f165f8c4
|
7
|
+
data.tar.gz: 7667ee7a964c55262104b954abd8289f7855b4107056041587702d76dbecab016425b139d49a3989ddcc53d7a905d781c029f39ca339cb122278d75542c46eae
|
data/lib/mailhandler.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
require_relative 'mailhandler/sender'
|
2
|
+
require_relative 'mailhandler/receiver'
|
3
|
+
|
4
|
+
require_relative 'mailhandler/receiving/notification/email'
|
5
|
+
require_relative 'mailhandler/receiving/notification/console'
|
6
|
+
|
7
|
+
module MailHandler
|
8
|
+
|
9
|
+
class Handler
|
10
|
+
|
11
|
+
attr_accessor :sender,
|
12
|
+
:receiver
|
13
|
+
|
14
|
+
def self.sender(type = :postmark_api)
|
15
|
+
|
16
|
+
verify_type(type, SENDER_TYPES)
|
17
|
+
sender = MailHandler::Sender.new(SENDER_TYPES[type].new)
|
18
|
+
yield(sender.dispatcher) if block_given?
|
19
|
+
|
20
|
+
sender
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def self.receiver(type = :folder, notifications = [])
|
26
|
+
|
27
|
+
verify_type(type, CHECKER_TYPES)
|
28
|
+
receiver = MailHandler::Receiver.new(CHECKER_TYPES[type].new)
|
29
|
+
add_receiving_notifications(receiver, notifications)
|
30
|
+
|
31
|
+
yield(receiver.checker) if block_given?
|
32
|
+
|
33
|
+
receiver
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.handler(sender, receiver)
|
38
|
+
|
39
|
+
handler = new
|
40
|
+
handler.sender = sender
|
41
|
+
handler.receiver = receiver
|
42
|
+
handler
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def self.add_receiving_notifications(receiver, notifications)
|
49
|
+
|
50
|
+
if (notifications - NOTIFICATION_TYPES.keys).empty?
|
51
|
+
|
52
|
+
notifications.each { |n| receiver.add_observer(NOTIFICATION_TYPES[n].new) }
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.verify_type(type, types)
|
59
|
+
|
60
|
+
raise StandardError, "Unknown type - #{type}, possible options: #{types.keys}" unless types.keys.include? type
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
CHECKER_TYPES = {
|
65
|
+
|
66
|
+
:folder => Receiving::FolderChecker,
|
67
|
+
:imap => Receiving::IMAPChecker
|
68
|
+
|
69
|
+
}
|
70
|
+
|
71
|
+
SENDER_TYPES = {
|
72
|
+
|
73
|
+
:postmark_api => Sending::PostmarkAPISender,
|
74
|
+
:postmark_batch_api => Sending::PostmarkBatchAPISender,
|
75
|
+
:smtp => Sending::SMTPSender
|
76
|
+
|
77
|
+
}
|
78
|
+
|
79
|
+
NOTIFICATION_TYPES = {
|
80
|
+
|
81
|
+
:console => Receiving::Notification::Console,
|
82
|
+
:email => Receiving::Notification::Email
|
83
|
+
|
84
|
+
}
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require_relative 'receiving/checker_folder'
|
2
|
+
require_relative 'receiving/checker_imap'
|
3
|
+
require_relative 'receiving/observer'
|
4
|
+
|
5
|
+
module MailHandler
|
6
|
+
|
7
|
+
class Receiver
|
8
|
+
|
9
|
+
include Receiving::Observer
|
10
|
+
|
11
|
+
attr_accessor :checker,
|
12
|
+
:search,
|
13
|
+
:search_max_duration
|
14
|
+
|
15
|
+
module DEFAULTS
|
16
|
+
|
17
|
+
MAX_SEARCH_DURATION = 240 # maximum time for search to last in [seconds]
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param [Hash] - search options
|
22
|
+
# @param [Time] - search started at Time
|
23
|
+
# @param [Time] - search finished at Time
|
24
|
+
# @param [int] - how long search lasted
|
25
|
+
# @param [int] - how long search can last
|
26
|
+
# @param [boolean] - result of search
|
27
|
+
# @param [Mail] - first email found
|
28
|
+
# @param [Array] - all emails found
|
29
|
+
Search = Struct.new( :options, :started_at, :finished_at, :duration, :max_duration, :result, :email, :emails)
|
30
|
+
|
31
|
+
def initialize(checker)
|
32
|
+
|
33
|
+
@checker = checker
|
34
|
+
@search_max_duration = DEFAULTS::MAX_SEARCH_DURATION
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
def find_email(options)
|
39
|
+
|
40
|
+
init_search_details(options)
|
41
|
+
|
42
|
+
while 1
|
43
|
+
|
44
|
+
received = checker.find(options) || search_time_expired?
|
45
|
+
update_search_details
|
46
|
+
notify_observers(search)
|
47
|
+
|
48
|
+
break if received
|
49
|
+
sleep 1
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
checker.search_result
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def init_search_details(options)
|
60
|
+
|
61
|
+
@search = Search.new
|
62
|
+
@search.options = options
|
63
|
+
@search.started_at = Time.now
|
64
|
+
@search.max_duration = @search_max_duration
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
def update_search_details
|
69
|
+
|
70
|
+
search.finished_at = Time.now
|
71
|
+
search.duration = search.finished_at - search.started_at
|
72
|
+
search.result = checker.search_result
|
73
|
+
search.emails = checker.found_emails
|
74
|
+
search.email = search.emails.first
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
def search_time_expired?
|
79
|
+
|
80
|
+
(Time.now - search.started_at) > @search_max_duration
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module MailHandler
|
2
|
+
|
3
|
+
module Receiving
|
4
|
+
|
5
|
+
#
|
6
|
+
# Email receiving checker interface. All email checking types need to implement it.
|
7
|
+
# @see MailHandler::Receiving::FolderChecker for example for one of implemented checkers.
|
8
|
+
#
|
9
|
+
# Checker interface is used for doing a single check whether email is in your inbox.
|
10
|
+
#
|
11
|
+
class Checker
|
12
|
+
|
13
|
+
attr_accessor :search_options,
|
14
|
+
:found_emails
|
15
|
+
|
16
|
+
AVAILABLE_SEARCH_OPTIONS = [
|
17
|
+
|
18
|
+
:by_subject,
|
19
|
+
:by_content,
|
20
|
+
:by_date,
|
21
|
+
:by_recipient,
|
22
|
+
:count,
|
23
|
+
:archive
|
24
|
+
|
25
|
+
]
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
|
29
|
+
# Default number of email results to return, and whether to archive emails.
|
30
|
+
@search_options = {:count => 10, :archive => false}
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
def find(options)
|
35
|
+
|
36
|
+
raise StandardError, 'Method not implemented'
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
def verify_and_set_search_options(options)
|
41
|
+
|
42
|
+
unless (options.keys - AVAILABLE_SEARCH_OPTIONS).empty?
|
43
|
+
raise StandardError, "#{(options.keys - AVAILABLE_SEARCH_OPTIONS)} - Incorrect search option values, options are #{AVAILABLE_SEARCH_OPTIONS}"
|
44
|
+
end
|
45
|
+
|
46
|
+
@search_options = search_options.merge options
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
def search_result
|
51
|
+
|
52
|
+
found_emails != nil and !found_emails.empty?
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require_relative 'checker.rb'
|
2
|
+
require_relative 'filter'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'mail'
|
5
|
+
|
6
|
+
module MailHandler
|
7
|
+
|
8
|
+
module Receiving
|
9
|
+
|
10
|
+
class FolderChecker < Checker
|
11
|
+
|
12
|
+
# folders in which emails will be searched for and managed
|
13
|
+
attr_accessor :inbox_folder,
|
14
|
+
:archive_folder
|
15
|
+
|
16
|
+
def initialize(inbox_folder = nil, archive_folder = nil)
|
17
|
+
|
18
|
+
super()
|
19
|
+
|
20
|
+
@inbox_folder = inbox_folder
|
21
|
+
@archive_folder = archive_folder
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
# check whether email is received by checking for an email in folder
|
26
|
+
def find(options)
|
27
|
+
|
28
|
+
verify_and_set_search_options(options)
|
29
|
+
email_files = find_files(search_options)
|
30
|
+
|
31
|
+
if email_files.empty?
|
32
|
+
|
33
|
+
@found_emails = []
|
34
|
+
|
35
|
+
else
|
36
|
+
|
37
|
+
@found_emails = read_found_emails(email_files, search_options[:count])
|
38
|
+
move_files(email_files) if search_options[:archive]
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
!@found_emails.empty?
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# filter options which need to be done by searching files
|
49
|
+
FILE_SEARCH_OPTIONS = {
|
50
|
+
|
51
|
+
:by_subject => Filter::ByContent,
|
52
|
+
:by_content => Filter::ByContent,
|
53
|
+
:by_date => Filter::ByDate,
|
54
|
+
:by_recipient => Filter::Email::ByRecipient
|
55
|
+
}
|
56
|
+
|
57
|
+
def search_pattern
|
58
|
+
|
59
|
+
@inbox_folder + '/*.*'
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
def read_found_emails(files, count)
|
64
|
+
|
65
|
+
files.first(count).map do |file|
|
66
|
+
|
67
|
+
email_content = File.read(file)
|
68
|
+
Mail.read_from_string(email_content)
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
# find files by FILE_SEARCH_OPTIONS options
|
75
|
+
# this will ignore filter criteria options which can't be done on files directly
|
76
|
+
def find_files(options)
|
77
|
+
|
78
|
+
files = Filter::Base.new.get(search_pattern)
|
79
|
+
|
80
|
+
options.each do |key, value|
|
81
|
+
|
82
|
+
files = (files & FILE_SEARCH_OPTIONS[key].new(value).get(search_pattern)) if FILE_SEARCH_OPTIONS[key] != nil
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
Filter::Base.sort(files)
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
def move_files(files)
|
91
|
+
|
92
|
+
setup_inbox_folders
|
93
|
+
|
94
|
+
files.each do |file|
|
95
|
+
|
96
|
+
file = File.basename(file)
|
97
|
+
|
98
|
+
if inbox_folder != archive_folder
|
99
|
+
|
100
|
+
FileUtils.mv("#{inbox_folder}/#{file}", "#{archive_folder}/#{file}")
|
101
|
+
|
102
|
+
else
|
103
|
+
|
104
|
+
FileUtils.rm_r "#{inbox_folder}/#{file}", :force => true
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
# create folders if they don't exist
|
113
|
+
def setup_inbox_folders
|
114
|
+
|
115
|
+
raise StandardError, 'Folder variables are not set' if inbox_folder.nil? or archive_folder.nil?
|
116
|
+
|
117
|
+
Dir::mkdir inbox_folder unless File.directory? inbox_folder
|
118
|
+
Dir::mkdir archive_folder unless File.directory? archive_folder
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'mail'
|
2
|
+
require_relative 'checker.rb'
|
3
|
+
# encoding: utf-8
|
4
|
+
|
5
|
+
module MailHandler
|
6
|
+
|
7
|
+
module Receiving
|
8
|
+
|
9
|
+
class IMAPChecker < Checker
|
10
|
+
|
11
|
+
AVAILABLE_SEARCH_OPTIONS = [
|
12
|
+
|
13
|
+
:by_subject,
|
14
|
+
:count,
|
15
|
+
:archive
|
16
|
+
|
17
|
+
]
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
|
21
|
+
super
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
# set email account from which we will read email
|
26
|
+
def imap_details(address, port, username, password, use_ssl)
|
27
|
+
|
28
|
+
Mail.defaults do
|
29
|
+
retriever_method :imap,
|
30
|
+
:address => address,
|
31
|
+
:port => port,
|
32
|
+
:user_name => username,
|
33
|
+
:password => password,
|
34
|
+
:enable_ssl => use_ssl
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
def find(options)
|
40
|
+
|
41
|
+
verify_and_set_search_options(options)
|
42
|
+
validate_options(options)
|
43
|
+
emails = find_emails(search_options)
|
44
|
+
|
45
|
+
@found_emails = []
|
46
|
+
|
47
|
+
unless emails.empty?
|
48
|
+
|
49
|
+
emails.each do |email|
|
50
|
+
|
51
|
+
found = email.subject.include? search_options[:by_subject]
|
52
|
+
@found_emails << email if found
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
!@found_emails.empty?
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def find_emails(options)
|
65
|
+
|
66
|
+
if options[:archive]
|
67
|
+
|
68
|
+
Mail.find_and_delete(:what => :last, :count => search_options[:count], :order => :desc, :keys => ['SUBJECT', search_options[:by_subject]])
|
69
|
+
|
70
|
+
else
|
71
|
+
|
72
|
+
Mail.find(:what => :last, :count => search_options[:count], :order => :desc, :keys => ['SUBJECT', search_options[:by_subject]])
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
def validate_options(options)
|
79
|
+
|
80
|
+
unless (options.keys - AVAILABLE_SEARCH_OPTIONS).empty?
|
81
|
+
raise StandardError, "#{(options.keys - AVAILABLE_SEARCH_OPTIONS)} - Not supported search option values for imap, options are #{AVAILABLE_SEARCH_OPTIONS}"
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|