multi_mail 0.0.1 → 0.0.2

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.
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