rockdove 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -2,9 +2,15 @@
2
2
 
3
3
  ### initial release
4
4
 
5
- * Connects to EWS mailbox
6
- * Fetches the mail items
7
- * Parses each item (Signatures, Replies, Forward)
8
- * Handles bounce types (Undeliverable,AutoReply)
9
- * Polls for every interval specified
10
- * Provides a template of the Daemon for easy plug & play
5
+ * [Feature] Connects to EWS mailbox
6
+ * [Feature] Fetches the mail items
7
+ * [Feature] Parses each item (Signatures, Replies, Forward)
8
+ * [Feature] Handles bounce types (Undeliverable,AutoReply)
9
+ * [Feature] Polls for every interval specified
10
+ * [Feature] Provides a template of the Daemon for easy plug & play
11
+
12
+ ## v0.1.0
13
+
14
+ * [Enhancement] Ignore Delivery Failure Notices
15
+ * [Feature] Ignore Mails from specific email(s) list
16
+ * [Enhancement] Handles line breaks issue by setting text only mode for email body
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- rockdove ![rockdove](http://kiran.gnufied.org/wp-content/uploads/2012/07/1341725724_bird.png) [![Build Status](https://secure.travis-ci.org/kiranh/rockdove.png)](http://travis-ci.org/kiranh/rockdove)
1
+ rockdove ![rockdove](http://kiran.gnufied.org/wp-content/uploads/2012/07/1341725724_bird.png) [![Build Status](https://secure.travis-ci.org/kiranh/rockdove.png?branch=master)](http://travis-ci.org/kiranh/rockdove)
2
2
  ========
3
3
 
4
4
  Incoming mail processing daemon for Exchange Web Services 1.0 (EWS). This Ruby Gem connects to the EWS mailbox, fetches the mail items, parses each mail item (Signatures, Replies, Forward), handles bounce types (Undeliverable,AutoReply) and polls the mailbox for every interval specified.
@@ -3,6 +3,7 @@ module Rockdove
3
3
  UNDELIVERABLE = /Undeliverable/i
4
4
  AUTO_REPLY = /Automatic reply/i
5
5
  SPAM = /SPAM/i
6
+ FAILURE = /Delivery(.+)Failure/i
6
7
 
7
8
  class << self
8
9
  attr_accessor :mail_stack, :inbox_connection
@@ -28,7 +29,7 @@ module Rockdove
28
29
  def send_rockdove_to_watch_mail(&block)
29
30
  Rockdove.logger.info "Rockdove on watch for new mail..."
30
31
  parsed_mails = group_of_mails
31
- if parsed_mails
32
+ if parsed_mails
32
33
  Rockdove.logger.info "Rockdove calling App block"
33
34
  block.call(parsed_mails)
34
35
  process()
@@ -36,32 +37,45 @@ module Rockdove
36
37
  end
37
38
 
38
39
  def group_of_mails
39
- return no_mail_alert unless fetch_from_box
40
- Rockdove.logger.info "Rockdove collected #{fetch_from_box.count} mail(s)."
40
+ return no_mail_alert unless fetch_from_box
41
+ Rockdove.logger.info "Rockdove collected #{@mail_stack.count} mail(s)."
41
42
  letters = RockdoveCollection.new
42
43
  @mail_stack.reverse.each do |item|
43
- if bounce_type_mail?(item)
44
+ if ignore_mail?(item) || bounce_type_mail?(item)
45
+ item.delete!
44
46
  @mail_stack.delete(item)
45
- else
46
- letters << retrieve_mail(@inbox_connection.get_item(item.id))
47
+ else
48
+ letters << retrieve_mail(item)
47
49
  end
48
50
  end
49
51
  letters
50
52
  end
51
53
 
54
+ def ignore_mail?(item)
55
+ email = item.from.email_address
56
+ ignore_list = Rockdove::Config.ignore_mails
57
+ return false unless ignore_list && !(ignore_list.empty?)
58
+ if ignore_list.include?(email)
59
+ Rockdove.logger.info "Rockdove detected #{email} under ignore mail list."
60
+ true
61
+ else
62
+ false
63
+ end
64
+ end
65
+
52
66
  def bounce_type_mail?(item)
53
67
  case item.subject
54
- when UNDELIVERABLE, AUTO_REPLY, SPAM
55
- Rockdove.logger.info "Rockdove deleted this mail: #{item.subject}."
56
- item.delete!
68
+ when UNDELIVERABLE, AUTO_REPLY, SPAM, FAILURE
69
+ Rockdove.logger.info "Rockdove deleting this mail: #{item.subject}."
57
70
  true
58
71
  else
59
72
  false
60
73
  end
61
74
  end
62
75
 
63
- def retrieve_mail(fetched_mail)
64
- Rockdove::ExchangeMail.new(fetched_mail)
76
+ def retrieve_mail(fetched_mail)
77
+ @inbox_connection ||= inbox
78
+ Rockdove::ExchangeMail.new(fetched_mail, @inbox_connection)
65
79
  end
66
80
 
67
81
  def no_mail_alert
@@ -1,7 +1,7 @@
1
1
  module Rockdove
2
2
  class Config
3
3
  class << self
4
- attr_accessor :url, :username, :password, :incoming_folder, :archive_folder, :watch_interval
4
+ attr_accessor :url, :username, :password, :incoming_folder, :archive_folder, :watch_interval, :ignore_mails
5
5
  end
6
6
 
7
7
  def self.configure(&block)
@@ -33,6 +33,10 @@ module Rockdove
33
33
  @watch_interval = value || 60
34
34
  end
35
35
 
36
+ def self.ews_ignore_mails(value)
37
+ @ignore_mails = value || []
38
+ end
39
+
36
40
  def self.connect
37
41
  Viewpoint::EWS::EWS.endpoint = @url
38
42
  Viewpoint::EWS::EWS.set_auth @username, @password
@@ -1,14 +1,14 @@
1
1
  module Rockdove
2
2
  class EmailParser
3
3
  HTML_REGEXP = /<\/?[^>]*>/
4
- FWD_REGEXP = /^(On(.+)wrote:.+\z)$/nm
4
+ FWD_REGEXP = /^(On(.+)wrote:.+\z)$/inm
5
5
  SIGNATURE_REGEXP = /^(Thanks(.+)Regards.+\z)$/inm
6
- REPLY_REGEXP = /^(From:(.+)Sent:.+\z)$/nm
6
+ REPLY_REGEXP = /^(From:(.+)Sent:.+\z)$/inm
7
+ SENT_VIA = /^(Sent(.+)Via.+\z)$/inm
7
8
  DASHES = "________________________________________"
8
9
 
9
10
  def self.parse_mail(mail, body_type)
10
- email_parser = new()
11
- email_parser.parse_email_tags(mail,body_type)
11
+ new().parse_email_tags(mail,body_type)
12
12
  end
13
13
 
14
14
  def parse_email_tags(mail,body_type)
@@ -16,13 +16,14 @@ module Rockdove
16
16
  return nil unless mail
17
17
  mail.gsub!(HTML_REGEXP, "").strip! if body_type == "HTML"
18
18
  parsed_content = EmailReplyParser.parse_reply(mail)
19
- content = choose_and_parse(parsed_content)
20
- handle_outlook_extra_line_breaks(content)
19
+ choose_and_parse(parsed_content)
21
20
  end
22
21
 
23
22
  def choose_and_parse(content)
24
23
  content.gsub!(DASHES,"")
25
- case content
24
+ case content
25
+ when SENT_VIA
26
+ content.gsub(SENT_VIA,"").strip!
26
27
  when SIGNATURE_REGEXP
27
28
  content.gsub(SIGNATURE_REGEXP,"").strip!
28
29
  when REPLY_REGEXP
@@ -33,14 +34,5 @@ module Rockdove
33
34
  content
34
35
  end
35
36
  end
36
-
37
- def handle_outlook_extra_line_breaks(content)
38
- if content && !(content.split("\n").first.include?(" "))
39
- content.sub!("\n", "")
40
- end
41
- content
42
- rescue
43
- content
44
- end
45
37
  end
46
38
  end
@@ -6,11 +6,14 @@ module Rockdove
6
6
  class ExchangeMail
7
7
  extend Forwardable
8
8
 
9
- def_delegators :@mail_item, :to_recipients, :date_time_created, :date_time_sent, :from
10
- def_delegators :@mail_item, :subject, :body, :has_attachments?, :attachments
9
+ def_delegators :@mail_item, :to_recipients, :date_time_created, :date_time_sent, :from, :body, :body_type
10
+ def_delegators :@mail_item, :subject, :body, :has_attachments?, :attachments, :text_only=, :text_only?
11
+
12
+ attr_accessor :connection
11
13
 
12
- def initialize(mail_item)
14
+ def initialize(mail_item, connection)
13
15
  @mail_item = mail_item
16
+ @connection = connection
14
17
  end
15
18
 
16
19
  def to_recipients
@@ -30,7 +33,12 @@ module Rockdove
30
33
  end
31
34
 
32
35
  def body
33
- parse_it(@mail_item.body, @mail_item.body_type) unless @mail_item.body.empty?
36
+ mail = get_items(@mail_item)
37
+ parse_it(mail.first.body, mail.first.body_type) unless mail.first.body.empty?
38
+ end
39
+
40
+ def get_items(mail)
41
+ @connection.get_items([mail.id], nil, {:item_shape => retrieve_text_type})
34
42
  end
35
43
 
36
44
  def attachments
@@ -47,6 +55,10 @@ module Rockdove
47
55
 
48
56
  def parse_it(mail_body, type)
49
57
  Rockdove::EmailParser.parse_mail(mail_body, type)
50
- end
58
+ end
59
+
60
+ def retrieve_text_type
61
+ {:base_shape=>"AllProperties", :body_type=>"Text"}
62
+ end
51
63
  end
52
64
  end
@@ -19,6 +19,7 @@ class RockdoveServer
19
19
  config.ews_archive_folder 'Archive' # by default, it deletes the mail after processing, mention ews_archive_folder if it
20
20
  # has to be archived to a different folder
21
21
  config.ews_watch_interval 60 # by default, the polling interval is 60
22
+ config.ews_ignore_mails ["postmaster@ewsdomain.com"] # an array of emails to be ignored
22
23
  end
23
24
  #Raad.env = "production"
24
25
  end
@@ -1,3 +1,3 @@
1
1
  module Rockdove
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -14,6 +14,7 @@ describe "CollectParseArchive" do
14
14
  mails = []
15
15
  mails << fetch_mail("new_mail") << fetch_mail("forwarded_mail") << fetch_mail("replied_mail")
16
16
  handle_inbox(mails)
17
+ mails.map {|mail| from_object = convert_to_class(mail.from) ; mail.should_receive(:from).at_most(3).times.and_return(from_object) }
17
18
  result = @mail_retriever.group_of_mails
18
19
  result.should be_an_instance_of(Rockdove::RockdoveCollection)
19
20
  result.count.should == 3
@@ -23,6 +24,7 @@ describe "CollectParseArchive" do
23
24
  it "should archive the mail at the end of collection cycle if user assigns ews move folder" do
24
25
  mail = fetch_mail("new_mail")
25
26
  handle_inbox(Array(mail))
27
+ convert_from_hash(mail)
26
28
  @mail_retriever.group_of_mails.should be_an_instance_of(Rockdove::RockdoveCollection)
27
29
  stub_destination_point
28
30
  lambda { @mail_retriever.process }.should_not raise_error
@@ -33,6 +35,7 @@ describe "CollectParseArchive" do
33
35
  @connection.archive_folder = nil
34
36
  mail = fetch_mail("new_mail")
35
37
  handle_inbox(Array(mail))
38
+ convert_from_hash(mail)
36
39
  @mail_retriever.group_of_mails.should be_an_instance_of(Rockdove::RockdoveCollection)
37
40
  lambda { @mail_retriever.process }.should_not raise_error
38
41
  @log_stream.string.should =~ /Rockdove delivered & deleted the mail/
@@ -40,6 +43,7 @@ describe "CollectParseArchive" do
40
43
 
41
44
  it "should parse the signature of the mail" do
42
45
  mail = fetch_mail("mail_type_text")
46
+ convert_from_hash(mail)
43
47
  parsed_content = Rockdove::EmailParser.parse_mail(mail.body, mail.body_type)
44
48
  parsed_content.should == "This is a new post"
45
49
  end
@@ -53,6 +57,7 @@ describe "CollectParseArchive" do
53
57
  it "should watch for new mail when called" do
54
58
  mail = fetch_mail("new_mail")
55
59
  handle_inbox(Array(mail))
60
+ convert_from_hash(mail)
56
61
  output = @mail_retriever.send_rockdove_to_watch_mail do |mail|
57
62
  true
58
63
  end
@@ -63,15 +68,26 @@ describe "CollectParseArchive" do
63
68
  it "should handle undeliverable bounce type" do
64
69
  mail = fetch_mail("auto_reply_mail")
65
70
  handle_inbox(Array(mail))
71
+ convert_from_hash(mail)
66
72
  @mail_retriever.group_of_mails.should be_an_instance_of(Rockdove::RockdoveCollection)
67
- @log_stream.string.should include("Rockdove deleted this mail: Automatic reply: Out of Office Message")
73
+ @log_stream.string.should include("Rockdove deleting this mail: Automatic reply: Out of Office Message")
74
+ end
75
+
76
+ it "should ignore mails when specified under ews_ignore_mails list" do
77
+ mail = fetch_mail("undeliverable_mail")
78
+ handle_inbox(Array(mail))
79
+ convert_from_hash(mail)
80
+ @mail_retriever.group_of_mails.should be_an_instance_of(Rockdove::RockdoveCollection)
81
+ @log_stream.string.should include("Rockdove detected postmaster@ewsdomain.com under ignore mail list.")
68
82
  end
69
83
 
70
84
  it "should handle auto reply bounce type" do
85
+ @connection.ews_ignore_mails([])
71
86
  mail = fetch_mail("undeliverable_mail")
72
87
  handle_inbox(Array(mail))
88
+ convert_from_hash(mail)
73
89
  @mail_retriever.group_of_mails.should be_an_instance_of(Rockdove::RockdoveCollection)
74
- @log_stream.string.should include("Rockdove deleted this mail: Undeliverable: New Post")
90
+ @log_stream.string.should include("Rockdove deleting this mail: Undeliverable: New Post")
75
91
  end
76
92
 
77
93
  def handle_inbox(mail_array)
@@ -39,7 +39,7 @@ is_read_receipt_requested:
39
39
  from:
40
40
  name:
41
41
  text: LastName, User (ewsdomain)
42
- email_address: User.LastName@ewsdomain.com
42
+ email_address: postmaster@ewsdomain.com
43
43
  routing_type:
44
44
  text: SMTP
45
45
  is_read:
@@ -7,6 +7,7 @@ describe "FetchIncomingMail" do
7
7
  @mail_retriever = Rockdove::CollectMail.new()
8
8
  @log_stream = StringIO.new
9
9
  Rockdove.stub!(:logger).and_return(Logger.new(@log_stream))
10
+ @test_server = Rockdove::EWS.new()
10
11
  end
11
12
 
12
13
  it "should validate the exchange server info provided" do
@@ -28,6 +29,7 @@ describe "FetchIncomingMail" do
28
29
  it "is trying to fetch a new forwarded mail" do
29
30
  mail = fetch_mail("forwarded_mail")
30
31
  mail.should be_an_instance_of(Rockdove::EWS)
32
+ handle_inbox(Array(mail))
31
33
  result = @mail_retriever.retrieve_mail(mail)
32
34
  result.should be_an_instance_of(Rockdove::ExchangeMail)
33
35
  result.body.should == "FYI"
@@ -39,6 +41,7 @@ describe "FetchIncomingMail" do
39
41
  it "is trying to fetch a new mail" do
40
42
  mail = fetch_mail("new_mail")
41
43
  mail.should be_an_instance_of(Rockdove::EWS)
44
+ handle_inbox(Array(mail))
42
45
  result = @mail_retriever.retrieve_mail(mail)
43
46
  result.should be_an_instance_of(Rockdove::ExchangeMail)
44
47
  result.body.should == "Hi, This is a new post"
@@ -50,6 +53,7 @@ describe "FetchIncomingMail" do
50
53
  it "is trying to fetch a new replied mail" do
51
54
  mail = fetch_mail("replied_mail")
52
55
  mail.should be_an_instance_of(Rockdove::EWS)
56
+ handle_inbox(Array(mail))
53
57
  result = @mail_retriever.retrieve_mail(mail)
54
58
  result.should be_an_instance_of(Rockdove::ExchangeMail)
55
59
  result.body.should == "This is a replied post from outlook"
@@ -65,13 +69,11 @@ describe "FetchIncomingMail" do
65
69
  result.date_time_sent.should == mail.date_time_sent
66
70
  end
67
71
 
68
- def convert_from_hash(mail)
69
- from_object = convert_to_class(mail.from)
70
- mail.should_receive(:from).twice.and_return(from_object)
71
- end
72
-
73
- def convert_to_class(hash)
74
- Hash2Class.new(hash)
72
+ def handle_inbox(mail_array)
73
+ @mail_retriever.should_receive(:inbox).at_most(:twice).and_return(@test_server)
74
+ @test_server.should_receive(:find_items).at_most(:twice).and_return(mail_array)
75
+ @test_server.should_receive(:get_items).at_most(:twice).and_return(mail_array)
76
+ @mail_retriever.retrieve_mail(mail_array.first).should be_an_instance_of(Rockdove::ExchangeMail)
75
77
  end
76
78
 
77
79
  end
@@ -21,6 +21,10 @@ module Rockdove
21
21
  item
22
22
  end
23
23
 
24
+ def get_items(mail,key,shape)
25
+ Array(mail)
26
+ end
27
+
24
28
  def move!(destination)
25
29
  response("Archived the Mail Item")
26
30
  end
@@ -42,6 +46,15 @@ class Hash2Class
42
46
  end
43
47
  end
44
48
 
49
+ def convert_from_hash(mail)
50
+ from_object = convert_to_class(mail.from)
51
+ mail.should_receive(:from).at_most(3).times.and_return(from_object)
52
+ end
53
+
54
+ def convert_to_class(hash)
55
+ Hash2Class.new(hash)
56
+ end
57
+
45
58
  def fetch_mail(name)
46
59
  YAML.load(fetch_mail_path(name.to_sym))
47
60
  end
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'simplecov'
2
2
  SimpleCov.start do
3
3
  add_filter '/vendor/bundle/'
4
+ add_filter '/spec'
4
5
  end
5
6
 
6
7
  require 'rockdove'
@@ -18,6 +19,7 @@ Rockdove::Config.configure do |config|
18
19
  config.ews_folder 'Inbox'
19
20
  config.ews_archive_folder 'Archive'
20
21
  config.ews_watch_interval 60
22
+ config.ews_ignore_mails ["postmaster@ewsdomain.com"]
21
23
  end
22
24
 
23
25
  RSpec.configure do |c|
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rockdove
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-08 00:00:00.000000000Z
12
+ date: 2012-07-11 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &86710060 !ruby/object:Gem::Requirement
16
+ requirement: &80745770 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *86710060
24
+ version_requirements: *80745770
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: guard-rspec
27
- requirement: &86709640 !ruby/object:Gem::Requirement
27
+ requirement: &80745510 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *86709640
35
+ version_requirements: *80745510
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rake
38
- requirement: &86709160 !ruby/object:Gem::Requirement
38
+ requirement: &80745180 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 0.9.2.2
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *86709160
46
+ version_requirements: *80745180
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: simplecov
49
- requirement: &86708860 !ruby/object:Gem::Requirement
49
+ requirement: &80744910 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *86708860
57
+ version_requirements: *80744910
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: raad
60
- requirement: &86708510 !ruby/object:Gem::Requirement
60
+ requirement: &80744630 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :runtime
67
67
  prerelease: false
68
- version_requirements: *86708510
68
+ version_requirements: *80744630
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: viewpoint
71
- requirement: &86708250 !ruby/object:Gem::Requirement
71
+ requirement: &80688550 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - =
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: 0.1.26
77
77
  type: :runtime
78
78
  prerelease: false
79
- version_requirements: *86708250
79
+ version_requirements: *80688550
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: email_reply_parser
82
- requirement: &86708020 !ruby/object:Gem::Requirement
82
+ requirement: &80688340 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,10 +87,10 @@ dependencies:
87
87
  version: '0'
88
88
  type: :runtime
89
89
  prerelease: false
90
- version_requirements: *86708020
90
+ version_requirements: *80688340
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: logger
93
- requirement: &86707770 !ruby/object:Gem::Requirement
93
+ requirement: &80688100 !ruby/object:Gem::Requirement
94
94
  none: false
95
95
  requirements:
96
96
  - - ! '>='
@@ -98,7 +98,7 @@ dependencies:
98
98
  version: '0'
99
99
  type: :runtime
100
100
  prerelease: false
101
- version_requirements: *86707770
101
+ version_requirements: *80688100
102
102
  description: Incoming mail processing daemon for Exchange Web Services (EWS). This
103
103
  is the Ruby Gem that does fetching, parsing and watching the mailbox for further
104
104
  processing.
@@ -152,7 +152,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
152
152
  version: '0'
153
153
  segments:
154
154
  - 0
155
- hash: -565949649
155
+ hash: -374666243
156
156
  required_rubygems_version: !ruby/object:Gem::Requirement
157
157
  none: false
158
158
  requirements:
@@ -161,10 +161,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
161
161
  version: '0'
162
162
  segments:
163
163
  - 0
164
- hash: -565949649
164
+ hash: -374666243
165
165
  requirements: []
166
166
  rubyforge_project: rockdove
167
- rubygems_version: 1.8.15
167
+ rubygems_version: 1.8.10
168
168
  signing_key:
169
169
  specification_version: 3
170
170
  summary: Incoming mail processing daemon for Exchange Web Services (EWS)