multi_mail 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/.travis.yml +4 -0
  2. data/.yardopts +4 -0
  3. data/Gemfile +1 -1
  4. data/README.md +107 -12
  5. data/Rakefile +75 -52
  6. data/lib/multi_mail/cloudmailin/receiver.rb +100 -0
  7. data/lib/multi_mail/mailgun/receiver.rb +74 -36
  8. data/lib/multi_mail/mailgun/sender.rb +14 -0
  9. data/lib/multi_mail/mandrill/receiver.rb +77 -35
  10. data/lib/multi_mail/mandrill/sender.rb +14 -0
  11. data/lib/multi_mail/postmark/receiver.rb +68 -0
  12. data/lib/multi_mail/postmark/sender.rb +14 -0
  13. data/lib/multi_mail/receiver/base.rb +125 -8
  14. data/lib/multi_mail/receiver.rb +10 -4
  15. data/lib/multi_mail/sender/base.rb +7 -0
  16. data/lib/multi_mail/sender.rb +10 -4
  17. data/lib/multi_mail/sendgrid/receiver.rb +42 -0
  18. data/lib/multi_mail/sendgrid/sender.rb +11 -0
  19. data/lib/multi_mail/service.rb +15 -4
  20. data/lib/multi_mail/simple/receiver.rb +15 -0
  21. data/lib/multi_mail/simple/sender.rb +14 -0
  22. data/lib/multi_mail/version.rb +1 -1
  23. data/lib/multi_mail.rb +71 -3
  24. data/multi_mail.gemspec +12 -5
  25. data/spec/cloudmailin/receiver_spec.rb +112 -0
  26. data/spec/fixtures/cloudmailin/json/spam.txt +59 -0
  27. data/spec/fixtures/cloudmailin/json/valid.txt +59 -0
  28. data/spec/fixtures/cloudmailin/multipart/spam.txt +135 -0
  29. data/spec/fixtures/cloudmailin/multipart/valid.txt +135 -0
  30. data/spec/fixtures/cloudmailin/raw/spam.txt +137 -0
  31. data/spec/fixtures/cloudmailin/raw/valid.txt +137 -0
  32. data/spec/fixtures/mailgun/parsed/invalid.txt +8 -0
  33. data/spec/fixtures/mailgun/parsed/missing.txt +8 -0
  34. data/spec/fixtures/mailgun/parsed/spam.txt +8 -0
  35. data/spec/fixtures/mailgun/parsed/valid.txt +187 -0
  36. data/spec/fixtures/mandrill/spam.txt +9 -0
  37. data/spec/fixtures/mandrill/valid.txt +10 -0
  38. data/spec/fixtures/multipart.txt +99 -0
  39. data/spec/fixtures/postmark/spam.txt +83 -0
  40. data/spec/fixtures/postmark/valid.txt +92 -0
  41. data/spec/fixtures/simple/valid.txt +4 -0
  42. data/spec/mailgun/receiver_spec.rb +105 -50
  43. data/spec/mailgun/sender_spec.rb +0 -0
  44. data/spec/mandrill/receiver_spec.rb +35 -35
  45. data/spec/mandrill/sender_spec.rb +0 -0
  46. data/spec/multi_mail_spec.rb +63 -0
  47. data/spec/postmark/receiver_spec.rb +60 -0
  48. data/spec/postmark/sender_spec.rb +0 -0
  49. data/spec/receiver/base_spec.rb +73 -8
  50. data/spec/sender/base_spec.rb +21 -0
  51. data/spec/service_spec.rb +2 -2
  52. data/spec/simple/receiver_spec.rb +36 -0
  53. data/spec/simple/sender_spec.rb +0 -0
  54. data/spec/spec_helper.rb +123 -10
  55. metadata +141 -21
  56. data/spec/fixtures/mailgun/invalid.txt +0 -8
  57. data/spec/fixtures/mailgun/missing.txt +0 -8
  58. data/spec/fixtures/mailgun/spam.txt +0 -8
  59. data/spec/fixtures/mailgun/valid.txt +0 -8
@@ -1,71 +1,113 @@
1
1
  module MultiMail
2
2
  module Receiver
3
+ # Mandrill's incoming email receiver.
4
+ #
5
+ # Mandrill uses an HTTP header to ensure a request originates from Mandrill.
6
+ #
7
+ # @see http://help.mandrill.com/entries/23704122-Authenticating-webhook-requests
3
8
  class Mandrill < MultiMail::Service
4
9
  include MultiMail::Receiver::Base
5
10
 
6
- requires :mandrill_api_key
11
+ recognizes :spamassassin_threshold
7
12
 
13
+ # Initializes a Mandrill incoming email receiver.
14
+ #
8
15
  # @param [Hash] options required and optional arguments
9
- # @option opts [String] :mandrill_api_key a Mandrill API key
16
+ # @option option [Float] :spamassassin_threshold the SpamAssassin score
17
+ # needed to flag a message as spam
10
18
  def initialize(options = {})
11
19
  super
12
- @mandrill_api_key = options[:mandrill_api_key]
20
+ @spamassassin_threshold = options[:spamassassin_threshold] || 5
13
21
  end
14
22
 
23
+ # Transforms the content of Mandrill's webhook into a list of messages.
24
+ #
15
25
  # @param [Hash] params the content of Mandrill's webhook
16
- # @return [Boolean] whether the request originates from Mandrill
17
- def valid?(params)
18
- JSON.parse(params['mandrill_events']).all? do |event|
26
+ # @return [Array<Mail::Message>] messages
27
+ # @see http://help.mandrill.com/entries/22092308-What-is-the-format-of-inbound-email-webhooks-
28
+ def transform(params)
29
+ # JSON is necessarily UTF-8.
30
+ JSON.parse(params['mandrill_events']).select do |event|
19
31
  event.fetch('event') == 'inbound'
20
- end
21
- end
32
+ end.map do |event|
33
+ msg = event['msg']
22
34
 
23
- # @param [Mail::Message] message a message
24
- # @return [Boolean] whether the message is spam
25
- def spam?(message)
26
- false
27
- end
35
+ headers = Multimap.new
36
+ msg['headers'].each do |key,value|
37
+ if Array === value
38
+ value.each do |v|
39
+ headers[key] = v
40
+ end
41
+ else
42
+ headers[key] = value
43
+ end
44
+ end
28
45
 
29
- # @param [Hash] params the content of Mandrill's webhook
30
- # @return [Array<Mail::Message>] messages
31
- # @todo parse attachments properly
32
- def transform(params)
33
- JSON.parse(params['mandrill_events']).map do |event|
46
+ this = self
34
47
  message = Mail.new do
35
- headers event['msg']['headers'].reject{|k,_| k=='Received'} # @todo
48
+ headers headers
36
49
 
37
50
  # The following are redundant with `message-headers`:
38
51
  #
39
- # address = Mail::Address.new event['msg']['from_email']
40
- # address.display_name = event['msg']['from_name']
52
+ # address = Mail::Address.new(msg['from_email'])
53
+ # address.display_name = msg['from_name']
41
54
  #
42
55
  # from address.format
43
- # to event['msg']['to'].flatten.compact
44
- # subject event['msg']['subject']
56
+ # to msg['to'].flatten.compact
57
+ # subject msg['subject']
45
58
 
46
59
  text_part do
47
- body event['msg']['text']
60
+ body msg['text']
48
61
  end
49
62
 
50
- html_part do
51
- content_type 'text/html; charset=UTF-8' # unsure about charset
52
- body event['msg']['html']
63
+ # If an email contains multiple HTML parts, Mandrill will only
64
+ # include the first HTML part in its `html` parameter. We therefore
65
+ # parse its `raw_msg` parameter to set the HTML part correctly.
66
+ html = this.class.condense(Mail.new(msg['raw_msg'])).parts.find do |part|
67
+ part.content_type == 'text/html; charset=UTF-8'
53
68
  end
54
- end
55
69
 
56
- # Extra Mandrill parameters. Discard `raw_msg`.
57
- [ 'email',
58
- 'tags',
59
- 'sender',
60
- ].each do |key|
61
- if !event['msg'][key].nil? && !event['msg'][key].empty?
62
- message[key] = event['msg'][key]
70
+ if html
71
+ html_part do
72
+ content_type 'text/html; charset=UTF-8'
73
+ body html.body.decoded
74
+ end
75
+ elsif msg.key?('html')
76
+ html_part do
77
+ content_type 'text/html; charset=UTF-8'
78
+ body msg['html']
79
+ end
80
+ end
81
+
82
+ if msg.key?('attachments')
83
+ msg['attachments'].each do |_,attachment|
84
+ add_file(:filename => attachment['name'], :content => attachment['content'])
85
+ end
63
86
  end
64
87
  end
65
88
 
89
+ # Extra Mandrill parameters. Discard `sender` and `tags`, which are
90
+ # null according to the docs, `matched_rules` within `spam_report`,
91
+ # `detail` within `spf`, which is just a human-readable version of
92
+ # `result`, and `raw_msg`.
93
+ message['ts'] = event['ts']
94
+ message['email'] = msg['email']
95
+ message['dkim-signed'] = msg['dkim']['signed'].to_s
96
+ message['dkim-valid'] = msg['dkim']['valid'].to_s
97
+ message['spam_report-score'] = msg['spam_report']['score']
98
+ message['spf-result'] = msg['spf']['result']
99
+
66
100
  message
67
101
  end
68
102
  end
103
+
104
+ # Returns whether a message is spam.
105
+ #
106
+ # @param [Mail::Message] message a message
107
+ # @return [Boolean] whether the message is spam
108
+ def spam?(message)
109
+ message['spam_report-score'] && message['spam_report-score'].value.to_f > @spamassassin_threshold
110
+ end
69
111
  end
70
112
  end
71
113
  end
@@ -0,0 +1,14 @@
1
+ module MultiMail
2
+ module Sender
3
+ class Mandrill < MultiMail::Service
4
+ include MultiMail::Sender::Base
5
+
6
+ #requires :
7
+
8
+ # @param [Hash] options required and optional arguments
9
+ def initialize(options = {})
10
+ super
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,68 @@
1
+ module MultiMail
2
+ module Receiver
3
+ # Postmark's incoming email receiver.
4
+ class Postmark < MultiMail::Service
5
+ include MultiMail::Receiver::Base
6
+
7
+ def transform(params)
8
+ headers = Multimap.new
9
+ params['Headers'].each do |header|
10
+ headers[header['Name']] = header['Value']
11
+ end
12
+
13
+ # Due to scoping issues, we can't call `transform_address` within `Mail.new`.
14
+ from = transform_address(params['FromFull'])
15
+ to = params['ToFull'].map{|hash| transform_address(hash)}
16
+ cc = params['CcFull'].map{|hash| transform_address(hash)}
17
+
18
+ message = Mail.new do
19
+ headers headers
20
+
21
+ from from
22
+ to to
23
+ cc cc
24
+ reply_to params['ReplyTo']
25
+ subject params['Subject']
26
+ date params['Date']
27
+
28
+ text_part do
29
+ body params['TextBody']
30
+ end
31
+
32
+ html_part do
33
+ content_type 'text/html; charset=UTF-8'
34
+ body CGI.unescapeHTML(params['HtmlBody'])
35
+ end
36
+
37
+ params['Attachments'].each do |attachment|
38
+ add_file(:filename => attachment['Name'], :content => Base64.decode64(attachment['Content']))
39
+ end
40
+ end
41
+
42
+ # Extra Postmark parameters.
43
+ %w(MailboxHash MessageID Tag).each do |key|
44
+ if params.key?(key) && !params[key].empty?
45
+ message[key] = params[key]
46
+ end
47
+ end
48
+
49
+ [message]
50
+ end
51
+
52
+ # @param [Mail::Message] message a message
53
+ # @return [Boolean] whether the message is spam
54
+ # @see http://developer.postmarkapp.com/developer-inbound-parse.html#spam
55
+ def spam?(message)
56
+ message['X-Spam-Status'].value == 'Yes'
57
+ end
58
+
59
+ private
60
+
61
+ def transform_address(hash)
62
+ address = Mail::Address.new(hash['Email'])
63
+ address.display_name = hash['Name']
64
+ address.to_s
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,14 @@
1
+ module MultiMail
2
+ module Sender
3
+ class Postmark < MultiMail::Service
4
+ include MultiMail::Sender::Base
5
+
6
+ #requires :
7
+
8
+ # @param [Hash] options required and optional arguments
9
+ def initialize(options = {})
10
+ super
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,6 +1,6 @@
1
1
  module MultiMail
2
2
  module Receiver
3
- # Abstract class for incoming email services.
3
+ # Abstract class for incoming email receivers.
4
4
  #
5
5
  # The `transform` instance method must be implemented in sub-classes. The
6
6
  # `valid?` and `spam?` instance methods may be implemented in sub-classes.
@@ -11,30 +11,39 @@ module MultiMail
11
11
  end
12
12
  end
13
13
 
14
- # @param [String,Hash] raw raw POST data or a params hash
14
+ # Ensures a request is authentic, parses it into a params hash, and
15
+ # transforms it into a list of messages.
16
+ #
17
+ # @param [String,Array,Hash,Rack::Request] raw raw POST data or a params hash
15
18
  # @return [Array<Mail::Message>] messages
16
- # @raises [ForgedRequest] if the request is not authentic
19
+ # @raise [ForgedRequest] if the request is not authentic
17
20
  def process(raw)
18
- params = self.class.parse raw
19
- if valid? params
20
- transform params
21
+ params = self.class.parse(raw)
22
+ if valid?(params)
23
+ transform(params)
21
24
  else
22
25
  raise ForgedRequest
23
26
  end
24
27
  end
25
28
 
29
+ # Returns whether a request is authentic.
30
+ #
26
31
  # @param [Hash] params the content of the provider's webhook
27
32
  # @return [Boolean] whether the request is authentic
28
33
  def valid?(params)
29
34
  true
30
35
  end
31
36
 
37
+ # Transforms the content of a provider's webhook into a list of messages.
38
+ #
32
39
  # @param [Hash] params the content of the provider's webhook
33
40
  # @return [Array<Mail::Message>] messages
34
41
  def transform(params)
35
42
  raise NotImplementedError
36
43
  end
37
44
 
45
+ # Returns whether a message is spam.
46
+ #
38
47
  # @param [Mail::Message] message a message
39
48
  # @return [Boolean] whether the message is spam
40
49
  def spam?(message)
@@ -42,23 +51,131 @@ module MultiMail
42
51
  end
43
52
 
44
53
  module ClassMethods
54
+ # ActionDispatch::Http::Request subclasses Rack::Request and turns
55
+ # attachment hashes into instances of ActionDispatch::Http::UploadedFile
56
+ # in Rails 3 and 4 and instances of ActionController::UploadedFile in
57
+ # Rails 2.3, both of which have the same interface.
58
+ #
59
+ # @param [ActionDispatch::Http::UploadedFile,ActionController::UploadedFile,Hash] attachment an attachment
60
+ # @return [Hash] arguments for `Mail::Message#add_file`
61
+ def add_file_arguments(attachment)
62
+ if Hash === attachment
63
+ {:filename => attachment[:filename], :content => attachment[:tempfile].read}
64
+ else
65
+ {:filename => attachment.original_filename, :content => attachment.read}
66
+ end
67
+ end
68
+
69
+ # Parses raw POST data into a params hash.
70
+ #
45
71
  # @param [String,Hash] raw raw POST data or a params hash
72
+ # @raise [ArgumentError] if the argument is not a string or a hash
46
73
  def parse(raw)
47
74
  case raw
48
75
  when String
49
- params = CGI.parse raw
50
- params.each do |key,value|
76
+ begin
77
+ JSON.parse(raw)
78
+ rescue JSON::ParserError
79
+ params = CGI.parse(raw)
80
+
81
+ # Flatten the parameters.
82
+ params.each do |key,value|
83
+ if Array === value && value.size == 1
84
+ params[key] = value.first
85
+ end
86
+ end
87
+
88
+ params
89
+ end
90
+ when Array
91
+ params = {}
92
+
93
+ # Collect the values for each key.
94
+ map = Multimap.new
95
+ raw.each do |key,value|
96
+ map[key] = value
97
+ end
98
+
99
+ # Flatten the parameters.
100
+ map.each do |key,value|
51
101
  if Array === value && value.size == 1
52
102
  params[key] = value.first
103
+ else
104
+ params[key] = value
53
105
  end
54
106
  end
107
+
55
108
  params
109
+ when Rack::Request
110
+ raw.params
56
111
  when Hash
57
112
  raw
58
113
  else
59
114
  raise ArgumentError, "Can't handle #{raw.class.name} input"
60
115
  end
61
116
  end
117
+
118
+ # Condenses a message's HTML parts to a single HTML part.
119
+ #
120
+ # @example
121
+ # flat = self.class.condense(message.dup)
122
+ #
123
+ # @param [Mail::Message] message a message with zero or more HTML parts
124
+ # @return [Mail::Message] the message with a single HTML part
125
+ def condense(message)
126
+ if message.multipart? && message.parts.any?(&:multipart?)
127
+ # Get the message parts as a flat array.
128
+ result = flatten(Mail.new, message.parts.dup)
129
+
130
+ # Rebuild the message's parts.
131
+ message.parts.clear
132
+
133
+ # Merge non-attachments with the same content type.
134
+ (result.parts - result.attachments).group_by(&:content_type).each do |content_type,group|
135
+ body = group.map{|part| part.body.decoded}.join
136
+
137
+ # Make content types match across all APIs.
138
+ if content_type == 'text/plain; charset=us-ascii'
139
+ # `text/plain; charset=us-ascii` is the default content type.
140
+ content_type = 'text/plain'
141
+ elsif content_type == 'text/html; charset=us-ascii'
142
+ content_type = 'text/html; charset=UTF-8'
143
+ body = body.encode('UTF-8') if body.respond_to?(:encode)
144
+ end
145
+
146
+ message.parts << Mail::Part.new({
147
+ :content_type => content_type,
148
+ :body => body,
149
+ })
150
+ end
151
+
152
+ # Add attachments last.
153
+ result.attachments.each do |part|
154
+ message.parts << part
155
+ end
156
+ end
157
+
158
+ message
159
+ end
160
+
161
+ # Flattens a hierarchy of message parts.
162
+ #
163
+ # @example
164
+ # flat = self.class.flatten(Mail.new, parts.dup)
165
+ #
166
+ # @param [Mail::Message] message a message
167
+ # @param [Mail::PartsList] parts parts to add to the message
168
+ # @return [Mail::Message] the message with all the parts
169
+ def flatten(message, parts)
170
+ parts.each do |part|
171
+ if part.multipart?
172
+ flatten(message, part.parts)
173
+ else
174
+ message.parts << part
175
+ end
176
+ end
177
+ message
178
+ end
62
179
  end
63
180
  end
64
181
  end
@@ -1,8 +1,10 @@
1
1
  module MultiMail
2
+ # Endpoint for initializing different incoming email receivers.
3
+ #
2
4
  # @see http://rdoc.info/gems/fog/Fog/Storage
3
5
  module Receiver
4
- autoload :Base, 'multi_mail/receiver/base'
5
-
6
+ # Initializes an incoming email receiver.
7
+ #
6
8
  # @example
7
9
  # require 'multi_mail'
8
10
  # service = MultiMail::Receiver.new({
@@ -11,8 +13,9 @@ module MultiMail
11
13
  # })
12
14
  #
13
15
  # @param [Hash] attributes required arguments
14
- # @option opts [String,Symbol] :provider a provider
15
- # @raises [ArgumentError] if the provider does not exist
16
+ # @option attributes [String,Symbol] :provider a provider
17
+ # @return [MultiMail::Service] an incoming email receiver
18
+ # @raise [ArgumentError] if the provider does not exist
16
19
  # @see Fog::Storage::new
17
20
  def self.new(attributes)
18
21
  attributes = attributes.dup # prevent delete from having side effects
@@ -32,6 +35,9 @@ module MultiMail
32
35
  when :sendgrid
33
36
  require 'multi_mail/sendgrid/receiver'
34
37
  MultiMail::Receiver::SendGrid.new(attributes)
38
+ when :simple
39
+ require 'multi_mail/simple/receiver'
40
+ MultiMail::Receiver::Simple.new(attributes)
35
41
  when :mock
36
42
  # for testing
37
43
  MultiMail::Receiver::Mock.new(attributes)
@@ -0,0 +1,7 @@
1
+ module MultiMail
2
+ module Sender
3
+ # Abstract class for outgoing email services.
4
+ module Base
5
+ end
6
+ end
7
+ end
@@ -1,8 +1,10 @@
1
1
  module MultiMail
2
+ # Endpoint for initializing different outgoing email senders.
3
+ #
2
4
  # @see http://rdoc.info/gems/fog/Fog/Storage
3
5
  module Sender
4
- autoload :Base, 'multi_mail/sender/base'
5
-
6
+ # Initializers an outgoing email sender.
7
+ #
6
8
  # @example
7
9
  # require 'multi_mail'
8
10
  # service = MultiMail::Sender.new({
@@ -11,8 +13,9 @@ module MultiMail
11
13
  # })
12
14
  #
13
15
  # @param [Hash] attributes required arguments
14
- # @option opts [String,Symbol] :provider a provider
15
- # @raises [ArgumentError] if the provider does not exist
16
+ # @option attributes [String,Symbol] :provider a provider
17
+ # @return [MultiMail::Service] an outgoing email sender
18
+ # @raise [ArgumentError] if the provider does not exist
16
19
  # @see Fog::Storage::new
17
20
  def self.new(attributes)
18
21
  attributes = attributes.dup # prevent delete from having side effects
@@ -29,6 +32,9 @@ module MultiMail
29
32
  when :sendgrid
30
33
  require 'multi_mail/sendgrid/sender'
31
34
  MultiMail::Sender::SendGrid.new(attributes)
35
+ when :simple
36
+ require 'multi_mail/simple/sender'
37
+ MultiMail::Sender::Simple.new(attributes)
32
38
  when :mock
33
39
  # for testing
34
40
  MultiMail::Sender::Mock.new(attributes)
@@ -0,0 +1,42 @@
1
+ module MultiMail
2
+ module Receiver
3
+ class SendGrid < MultiMail::Service
4
+ include MultiMail::Receiver::Base
5
+
6
+ requires :sendgrid_username
7
+ requires :sendgrid_password
8
+ recognizes :http_post_format
9
+ attr_reader :http_post_format
10
+
11
+ def initialize(options = {})
12
+ super
13
+ @sendgrid_username = options[:sendgrid_username]
14
+ @sendgrid_password = options[:sendgrid_password]
15
+ @http_post_format = options[:http_post_format]
16
+ end
17
+
18
+ def transform(params)
19
+ attachments = 1.upto(params['attachments'].to_i).map do |num|
20
+ attachment_from_params(params["attachment#{num}"])
21
+ end
22
+
23
+ @message = Mail.new do
24
+ header params['headers']
25
+
26
+ body params['text']
27
+
28
+ html_part do
29
+ content_type 'text/html; charset=UTF-8'
30
+ body params['html']
31
+ end if params['html']
32
+
33
+ attachments.each do |attachment|
34
+ add_file(attachment)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+
@@ -0,0 +1,11 @@
1
+ module MultiMail
2
+ module Sender
3
+ class SendGrid < MultiMail::Service
4
+ include MultiMail::Sender::base
5
+
6
+ def initialize(options = {})
7
+ super
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,6 +1,10 @@
1
1
  module MultiMail
2
+ # Interacts with email APIs to send or receive email.
3
+ #
2
4
  # @see http://rdoc.info/gems/fog/Fog/Service
3
5
  class Service
6
+ # Initializers an email API service.
7
+ #
4
8
  # @param [Hash] options optional arguments
5
9
  def initialize(options = {})
6
10
  self.class.validate_options(options)
@@ -15,6 +19,8 @@ module MultiMail
15
19
  requirements.concat(args)
16
20
  end
17
21
 
22
+ # Returns the list of required arguments.
23
+ #
18
24
  # @return [Array] a list of required arguments
19
25
  # @see Fog::Service::requirements
20
26
  def requirements
@@ -29,15 +35,20 @@ module MultiMail
29
35
  recognized.concat(args)
30
36
  end
31
37
 
38
+ # Returns the list of optional arguments.
39
+ #
32
40
  # @return [Array] a list of optional arguments
33
41
  # @see Fog::Service::recognized
34
42
  def recognized
35
43
  @recognized ||= []
36
44
  end
37
45
 
46
+ # Ensures that required arguments are present and that optional arguments
47
+ # are recognized.
48
+ #
38
49
  # @param [Hash] options arguments
39
- # @raises [ArgumentError] if can't find required arguments or can't
40
- # recognize optional arguments
50
+ # @raise [ArgumentError] if it can't find a required argument or can't
51
+ # recognize an optional argument
41
52
  # @see Fog::Service::validate_options
42
53
  def validate_options(options)
43
54
  keys = []
@@ -48,13 +59,13 @@ module MultiMail
48
59
  end
49
60
  missing = requirements - keys
50
61
  unless missing.empty?
51
- raise ArgumentError, "Missing required arguments: #{missing.join(', ')}"
62
+ raise ArgumentError, "Missing required arguments: #{missing.map(&:to_s).sort.join(', ')}"
52
63
  end
53
64
 
54
65
  unless recognizes.empty?
55
66
  unrecognized = options.keys - requirements - recognized
56
67
  unless unrecognized.empty?
57
- raise ArgumentError, "Unrecognized arguments: #{unrecognized.join(', ')}"
68
+ raise ArgumentError, "Unrecognized arguments: #{unrecognized.map(&:to_s).sort.join(', ')}"
58
69
  end
59
70
  end
60
71
  end
@@ -0,0 +1,15 @@
1
+ module MultiMail
2
+ module Receiver
3
+ class Simple < MultiMail::Service
4
+ include MultiMail::Receiver::Base
5
+ # Expects the value of the "message" query string parameter to be a raw
6
+ # email message parsable by the Mail gem.
7
+ #
8
+ # @param [Hash] params the content of the webhook
9
+ # @return [Array<Mail::Message>] messages
10
+ def transform(params)
11
+ [Mail.new(params['message'])]
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module MultiMail
2
+ module Sender
3
+ class Simple < MultiMail::Service
4
+ include MultiMail::Sender::Base
5
+
6
+ #requires :
7
+
8
+ # @param [Hash] options required and optional arguments
9
+ def initialize(options = {})
10
+ super
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,3 +1,3 @@
1
1
  module MultiMail
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end