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.
- data/.travis.yml +4 -0
- data/.yardopts +4 -0
- data/Gemfile +1 -1
- data/README.md +107 -12
- data/Rakefile +75 -52
- data/lib/multi_mail/cloudmailin/receiver.rb +100 -0
- data/lib/multi_mail/mailgun/receiver.rb +74 -36
- data/lib/multi_mail/mailgun/sender.rb +14 -0
- data/lib/multi_mail/mandrill/receiver.rb +77 -35
- data/lib/multi_mail/mandrill/sender.rb +14 -0
- data/lib/multi_mail/postmark/receiver.rb +68 -0
- data/lib/multi_mail/postmark/sender.rb +14 -0
- data/lib/multi_mail/receiver/base.rb +125 -8
- data/lib/multi_mail/receiver.rb +10 -4
- data/lib/multi_mail/sender/base.rb +7 -0
- data/lib/multi_mail/sender.rb +10 -4
- data/lib/multi_mail/sendgrid/receiver.rb +42 -0
- data/lib/multi_mail/sendgrid/sender.rb +11 -0
- data/lib/multi_mail/service.rb +15 -4
- data/lib/multi_mail/simple/receiver.rb +15 -0
- data/lib/multi_mail/simple/sender.rb +14 -0
- data/lib/multi_mail/version.rb +1 -1
- data/lib/multi_mail.rb +71 -3
- data/multi_mail.gemspec +12 -5
- data/spec/cloudmailin/receiver_spec.rb +112 -0
- data/spec/fixtures/cloudmailin/json/spam.txt +59 -0
- data/spec/fixtures/cloudmailin/json/valid.txt +59 -0
- data/spec/fixtures/cloudmailin/multipart/spam.txt +135 -0
- data/spec/fixtures/cloudmailin/multipart/valid.txt +135 -0
- data/spec/fixtures/cloudmailin/raw/spam.txt +137 -0
- data/spec/fixtures/cloudmailin/raw/valid.txt +137 -0
- data/spec/fixtures/mailgun/parsed/invalid.txt +8 -0
- data/spec/fixtures/mailgun/parsed/missing.txt +8 -0
- data/spec/fixtures/mailgun/parsed/spam.txt +8 -0
- data/spec/fixtures/mailgun/parsed/valid.txt +187 -0
- data/spec/fixtures/mandrill/spam.txt +9 -0
- data/spec/fixtures/mandrill/valid.txt +10 -0
- data/spec/fixtures/multipart.txt +99 -0
- data/spec/fixtures/postmark/spam.txt +83 -0
- data/spec/fixtures/postmark/valid.txt +92 -0
- data/spec/fixtures/simple/valid.txt +4 -0
- data/spec/mailgun/receiver_spec.rb +105 -50
- data/spec/mailgun/sender_spec.rb +0 -0
- data/spec/mandrill/receiver_spec.rb +35 -35
- data/spec/mandrill/sender_spec.rb +0 -0
- data/spec/multi_mail_spec.rb +63 -0
- data/spec/postmark/receiver_spec.rb +60 -0
- data/spec/postmark/sender_spec.rb +0 -0
- data/spec/receiver/base_spec.rb +73 -8
- data/spec/sender/base_spec.rb +21 -0
- data/spec/service_spec.rb +2 -2
- data/spec/simple/receiver_spec.rb +36 -0
- data/spec/simple/sender_spec.rb +0 -0
- data/spec/spec_helper.rb +123 -10
- metadata +141 -21
- data/spec/fixtures/mailgun/invalid.txt +0 -8
- data/spec/fixtures/mailgun/missing.txt +0 -8
- data/spec/fixtures/mailgun/spam.txt +0 -8
- data/spec/fixtures/mailgun/valid.txt +0 -8
data/.travis.yml
CHANGED
data/.yardopts
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,22 +1,19 @@
|
|
1
1
|
# MultiMail: easily switch between email APIs
|
2
2
|
|
3
|
+
[](http://travis-ci.org/opennorth/multi_mail)
|
3
4
|
[](https://gemnasium.com/opennorth/multi_mail)
|
4
|
-
[](https://coveralls.io/r/opennorth/multi_mail)
|
6
|
+
[](https://codeclimate.com/github/opennorth/multi_mail)
|
5
7
|
|
6
8
|
Many providers – including [Cloudmailin](http://www.cloudmailin.com/), [Mailgun](http://www.mailgun.com/), [Mandrill](http://mandrill.com/), [Postmark](http://postmarkapp.com/) and [SendGrid](http://sendgrid.com/) – offer APIs to send, receive, parse and forward email. MultiMail lets you easily switch between these APIs.
|
7
9
|
|
8
10
|
## Usage
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
:mailgun_api_key => 'key-xxxxxxxxxxxxxxxxxxxxxxx-x-xxxxxx',
|
16
|
-
})
|
17
|
-
|
18
|
-
message = service.process data # raw POST data or params hash
|
19
|
-
```
|
12
|
+
require 'multi_mail'
|
13
|
+
|
14
|
+
service = MultiMail::Receiver.new(:provider => 'mandrill')
|
15
|
+
|
16
|
+
message = service.process data # raw POST data or params hash
|
20
17
|
|
21
18
|
`message` is an array of [Mail::Message](https://github.com/mikel/mail) instances.
|
22
19
|
|
@@ -26,13 +23,111 @@ Incoming email:
|
|
26
23
|
|
27
24
|
* [Mailgun](http://www.mailgun.com/)
|
28
25
|
* [Mandrill](http://mandrill.com/)
|
26
|
+
* [Postmark](http://postmarkapp.com/)
|
27
|
+
* [Cloudmailin](http://www.cloudmailin.com/)
|
28
|
+
|
29
|
+
Any additional information provided by an API is added to the message as a header. For example, Mailgun provides `stripped-text`, which is the message body without quoted parts or signature block. You can access it with `message['stripped-text'].value`.
|
30
|
+
|
31
|
+
## Cloudmailin
|
32
|
+
|
33
|
+
service = MultiMail::Receiver.new({
|
34
|
+
:provider => 'cloudmailin',
|
35
|
+
})
|
36
|
+
|
37
|
+
The default HTTP POST format is `raw`. Add a `:http_post_format` option to change the HTTP POST format, with possible values of `"multipart"`, `"json"` or `"raw"` (default). (The [original format](http://docs.cloudmailin.com/http_post_formats/original/) is deprecated.) For example:
|
38
|
+
|
39
|
+
service = MultiMail::Receiver.new({
|
40
|
+
:provider => 'cloudmailin',
|
41
|
+
:http_post_format => 'raw',
|
42
|
+
})
|
43
|
+
|
44
|
+
**Note:** [MultiMail doesn't yet support Cloudmailin's URL attachments (attachment stores).](https://github.com/opennorth/multi_mail/issues/11) Please use regular attachments (always the case if you use the `raw` format) if you are using MultiMail.
|
45
|
+
|
46
|
+
**2013-04-15:** If an email contains multiple HTML parts and you are using the `multipart` or `json` HTTP POST formats, Cloudmailin will only include the first HTML part in its `html` parameter. Use the `raw` format to avoid data loss. Cloudmailin also removes a newline from the end of each attachment.
|
47
|
+
|
48
|
+
### Additional information provided by the API
|
49
|
+
|
50
|
+
See [Cloudmailin's documentation](http://docs.cloudmailin.com/http_post_formats/):
|
51
|
+
|
52
|
+
* `reply_plain`
|
53
|
+
* `spf-result`
|
54
|
+
|
55
|
+
## Mailgun
|
56
|
+
|
57
|
+
service = MultiMail::Receiver.new({
|
58
|
+
:provider => 'mailgun',
|
59
|
+
:mailgun_api_key => 'key-xxxxxxxxxxxxxxxxxxxxxxx-x-xxxxxx',
|
60
|
+
})
|
61
|
+
|
62
|
+
If you have a route with a URL ending with "mime" and you are using the raw MIME format, add a `:http_post_format => 'raw'` option. For example:
|
63
|
+
|
64
|
+
service = MultiMail::Receiver.new({
|
65
|
+
:provider => 'mailgun',
|
66
|
+
:mailgun_api_key => 'key-xxxxxxxxxxxxxxxxxxxxxxx-x-xxxxxx',
|
67
|
+
:http_post_format => 'raw',
|
68
|
+
})
|
69
|
+
|
70
|
+
**2013-04-15:** Mailgun's `stripped-text` and `stripped-html` parameters do not return the same parts of the message. `stripped-html` sometimes incorrectly drops non-quoted, non-signature parts of the message; `stripped-text` doesn't.
|
71
|
+
|
72
|
+
### Additional information provided by the API
|
29
73
|
|
30
|
-
[
|
74
|
+
See [Mailgun's documentation](http://documentation.mailgun.net/user_manual.html#parsed-messages-parameters):
|
75
|
+
|
76
|
+
* `stripped-text`
|
77
|
+
* `stripped-signature`
|
78
|
+
* `stripped-html`
|
79
|
+
* `content-id-map`
|
80
|
+
|
81
|
+
## Mandrill
|
82
|
+
|
83
|
+
service = MultiMail::Receiver.new({
|
84
|
+
:provider => 'mandrill',
|
85
|
+
})
|
86
|
+
|
87
|
+
The default SpamAssassin score needed to flag an email as spam is `5`. Add a `:spamassassin_threshold` option to increase or decrease it. For example:
|
88
|
+
|
89
|
+
service = MultiMail::Receiver.new({
|
90
|
+
:provider => 'mandrill',
|
91
|
+
:spamassassin_threshold => 4.5,
|
92
|
+
})
|
93
|
+
|
94
|
+
**2013-04-15:** If an email contains multiple HTML parts, Mandrill will only include the first HTML part in its `html` parameter. We therefore parse its `raw_msg` parameter to set the HTML part correctly. Mandrill also adds a newline to the end of each message part.
|
95
|
+
|
96
|
+
### Additional information provided by the API
|
97
|
+
|
98
|
+
See [Mandrill's documentation](http://help.mandrill.com/entries/22092308-What-is-the-format-of-inbound-email-webhooks-):
|
99
|
+
|
100
|
+
* `ts`
|
101
|
+
* `email`
|
102
|
+
* `dkim-signed`
|
103
|
+
* `dkim-valid`
|
104
|
+
* `spam_report-score`
|
105
|
+
* `spf-result`
|
106
|
+
|
107
|
+
## Postmark
|
108
|
+
|
109
|
+
service = MultiMail::Receiver.new({
|
110
|
+
:provider => 'postmark',
|
111
|
+
})
|
112
|
+
|
113
|
+
**2013-05-15:** If an email contains multiple HTML parts, Postmark will only include the first HTML part in its `HtmlBody` parameter. You cannot avoid this loss of data. Postmark is therefore not recommended.
|
114
|
+
|
115
|
+
### Additional information provided by the API
|
116
|
+
|
117
|
+
See [Postmark's documentation](http://developer.postmarkapp.com/developer-inbound-parse.html#mailboxhash):
|
118
|
+
|
119
|
+
* `MailboxHash`
|
120
|
+
* `MessageID`
|
121
|
+
* `Tag`
|
31
122
|
|
32
123
|
## Bugs? Questions?
|
33
124
|
|
34
125
|
This gem's main repository is on GitHub: [http://github.com/opennorth/multi_mail](http://github.com/opennorth/multi_mail), where your contributions, forks, bug reports, feature requests, and feedback are greatly welcomed.
|
35
126
|
|
127
|
+
## Acknowledgements
|
128
|
+
|
129
|
+
This gem is developed by [Open North](http://www.opennorth.ca/) through a partnership with the [Participatory Politics Foundation](http://www.participatorypolitics.org/).
|
130
|
+
|
36
131
|
## Copyright
|
37
132
|
|
38
133
|
This gem re-uses code from [fog](https://github.com/fog/fog), released under the MIT license.
|
data/Rakefile
CHANGED
@@ -21,80 +21,103 @@ def credentials
|
|
21
21
|
@credentials ||= YAML.load_file File.expand_path(File.join(File.dirname(__FILE__), 'api_keys.yml'))
|
22
22
|
end
|
23
23
|
|
24
|
-
|
24
|
+
desc 'Create a Mailgun catch-all route forwarding to a postbin'
|
25
|
+
task :mailgun do
|
26
|
+
require 'securerandom'
|
25
27
|
require 'json'
|
26
28
|
require 'rest-client'
|
27
29
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
bin_url = "http://requestb.in/#{bin_name}"
|
30
|
+
def bin_url_and_action
|
31
|
+
bin_name = JSON.load(RestClient.post('http://requestb.in/api/v1/bins', {}))['name']
|
32
|
+
["http://requestb.in/#{bin_name}", %(forward("#{bin_url}"))]
|
33
|
+
end
|
33
34
|
|
34
|
-
|
35
|
-
|
35
|
+
base_url = "https://api:#{credentials[:mailgun_api_key]}@api.mailgun.net/v2"
|
36
|
+
domain = JSON.load(RestClient.get("#{base_url}/domains"))['items'].first
|
36
37
|
|
37
|
-
|
38
|
+
if domain
|
39
|
+
domain_name = domain['name']
|
40
|
+
else
|
41
|
+
domain_name = "#{SecureRandom.base64(4).tr('+/=lIO0', 'pqrsxyz')}.mailgun.com"
|
42
|
+
puts "Creating the #{domain_name} domain..."
|
43
|
+
RestClient.post("#{base_url}/domains", :name => domain_name)
|
44
|
+
end
|
38
45
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
end
|
46
|
+
catch_all_route = JSON.load(RestClient.get("#{base_url}/routes"))['items'].find do |route|
|
47
|
+
route['expression'] == 'catch_all()'
|
48
|
+
end
|
43
49
|
|
44
|
-
|
45
|
-
|
50
|
+
if catch_all_route
|
51
|
+
action = catch_all_route['actions'].find do |action|
|
52
|
+
action[%r{\Aforward\("(http://requestb\.in/\w+)"\)\z}]
|
46
53
|
end
|
47
54
|
|
48
|
-
if
|
49
|
-
|
50
|
-
puts "Updating the catch_all() route..."
|
51
|
-
JSON.load(RestClient.put("#{base_url}/routes/#{route['id']}", :action => action))
|
52
|
-
end
|
55
|
+
if action
|
56
|
+
bin_url = $1
|
53
57
|
else
|
54
|
-
|
55
|
-
|
58
|
+
bin_url, action = bin_url_and_action
|
59
|
+
puts "Updating the catch_all() route..."
|
60
|
+
JSON.load(RestClient.put("#{base_url}/routes/#{catch_all_route['id']}", :action => action))
|
56
61
|
end
|
57
|
-
|
58
|
-
|
62
|
+
else
|
63
|
+
bin_url, action = bin_url_and_action
|
64
|
+
puts "Creating a catch_all() route..."
|
65
|
+
JSON.load(RestClient.post("#{base_url}/routes", :expression => 'catch_all()', :action => action))
|
59
66
|
end
|
67
|
+
|
68
|
+
puts "The catchall route for #{domain_name} POSTs to #{bin_url}?inspect"
|
60
69
|
end
|
61
70
|
|
62
|
-
|
71
|
+
desc 'Ensure a Mandrill catch-all route forwarding to a postbin'
|
72
|
+
task :mandrill do
|
63
73
|
require 'mandrill'
|
64
74
|
|
65
|
-
|
66
|
-
|
67
|
-
end
|
75
|
+
api = Mandrill::API.new(credentials[:mandrill_api_key])
|
76
|
+
domain = api.inbound.domains.first
|
68
77
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
end
|
78
|
+
if domain
|
79
|
+
domain_name = domain['domain']
|
80
|
+
routes = api.inbound.routes domain_name
|
81
|
+
match = routes.find{|route| route['pattern'] == '*'}
|
74
82
|
|
75
|
-
|
76
|
-
|
83
|
+
puts "The MX for #{domain_name} is not valid" unless domain['valid_mx']
|
84
|
+
puts "Add a catchall (*) route for #{domain_name}" if match.nil?
|
85
|
+
puts "The catchall route for #{domain_name} POSTs to #{match['url']}?inspect"
|
86
|
+
else
|
87
|
+
abort 'Add an inbound domain at https://mandrillapp.com/ or, if you already have your MX records set up, by sending an email through Mandrill'
|
77
88
|
end
|
89
|
+
end
|
78
90
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
abort "ENV['DOMAIN'] must be one of #{mandrill_domains.keys.join ', '}"
|
85
|
-
end
|
91
|
+
desc 'Create a Postmark route forwarding to a postbin'
|
92
|
+
task :postmark do
|
93
|
+
require 'json'
|
94
|
+
require 'postmark'
|
95
|
+
require 'rest-client'
|
86
96
|
|
87
|
-
|
88
|
-
abort "#{ENV['DOMAIN']} must be one of #{mandrill_domains.keys.join ', '}"
|
89
|
-
end
|
97
|
+
api = Postmark::ApiClient.new(credentials[:postmark_api_key])
|
90
98
|
|
91
|
-
|
92
|
-
puts "The MX for #{mandrill_domain} is not valid"
|
93
|
-
end
|
99
|
+
info = api.server_info
|
94
100
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
101
|
+
if info.key?(:inbound_hook_url)
|
102
|
+
url = info[:inbound_hook_url]
|
103
|
+
else
|
104
|
+
bin_name = JSON.load(RestClient.post('http://requestb.in/api/v1/bins', {}))['name']
|
105
|
+
url = "http://requestb.in/#{bin_name}"
|
106
|
+
api.update_server_info :inbound_hook_url => url
|
99
107
|
end
|
108
|
+
|
109
|
+
puts "#{info[:inbound_hash]}@inbound.postmarkapp.com POSTs to #{url}?inspect"
|
110
|
+
end
|
111
|
+
|
112
|
+
desc 'POST a test fixture to an URL'
|
113
|
+
task :http_post, :url, :fixture do |t,args|
|
114
|
+
require 'rest-client'
|
115
|
+
|
116
|
+
contents = File.read(args[:fixture])
|
117
|
+
io = StringIO.new(contents)
|
118
|
+
socket = Net::BufferedIO.new(io)
|
119
|
+
response = Net::HTTPResponse.read_new(socket)
|
120
|
+
body = contents[/(?:\r?\n){2,}(.+)\z/m, 1]
|
121
|
+
|
122
|
+
puts RestClient.post(args[:url], body, :content_type => response.header['content-type'])
|
100
123
|
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module MultiMail
|
2
|
+
module Receiver
|
3
|
+
# Cloudmailin's incoming email receiver.
|
4
|
+
#
|
5
|
+
# Cloudmailin recommends using basic authentication over HTTPS to ensure
|
6
|
+
# that a request originates from Cloudmailin.
|
7
|
+
#
|
8
|
+
# @see http://docs.cloudmailin.com/receiving_email/securing_your_email_url_target/
|
9
|
+
class Cloudmailin < MultiMail::Service
|
10
|
+
include MultiMail::Receiver::Base
|
11
|
+
|
12
|
+
recognizes :http_post_format
|
13
|
+
|
14
|
+
# Initializes a Cloudmailin incoming email receiver.
|
15
|
+
#
|
16
|
+
# @param [Hash] options required and optional arguments
|
17
|
+
# @option options [String] :http_post_format "multipart", "json" or "raw"
|
18
|
+
def initialize(options = {})
|
19
|
+
super
|
20
|
+
@http_post_format = options[:http_post_format]
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param [Hash] params the content of Cloudmailin's webhook
|
24
|
+
# @return [Array<Mail::Message>] messages
|
25
|
+
# @see http://docs.cloudmailin.com/http_post_formats/multipart/
|
26
|
+
# @see http://docs.cloudmailin.com/http_post_formats/json/
|
27
|
+
# @see http://docs.cloudmailin.com/http_post_formats/raw/
|
28
|
+
def transform(params)
|
29
|
+
case @http_post_format
|
30
|
+
when 'raw', '', nil
|
31
|
+
message = self.class.condense(Mail.new(params['message']))
|
32
|
+
|
33
|
+
# Extra Cloudmailin parameters.
|
34
|
+
message['spf-result'] = params['envelope']['spf']['result']
|
35
|
+
|
36
|
+
# Discard rest of `envelope`: `from`, `to`, `recipients`,
|
37
|
+
# `helo_domain` and `remote_ip`.
|
38
|
+
[message]
|
39
|
+
when 'multipart', 'json'
|
40
|
+
headers = Multimap.new
|
41
|
+
params['headers'].each do |key,value|
|
42
|
+
if Array === value
|
43
|
+
value.each do |v|
|
44
|
+
headers[key] = v
|
45
|
+
end
|
46
|
+
else
|
47
|
+
headers[key] = value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Mail changes `self`.
|
52
|
+
http_post_format = @http_post_format
|
53
|
+
|
54
|
+
this = self
|
55
|
+
message = Mail.new do
|
56
|
+
headers headers
|
57
|
+
|
58
|
+
text_part do
|
59
|
+
body params['plain']
|
60
|
+
end
|
61
|
+
|
62
|
+
if params.key?('html')
|
63
|
+
html_part do
|
64
|
+
content_type 'text/html; charset=UTF-8'
|
65
|
+
body params['html']
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
if params.key?('attachments')
|
70
|
+
if http_post_format == 'json'
|
71
|
+
params['attachments'].each do |attachment|
|
72
|
+
add_file(:filename => attachment['file_name'], :content => Base64.decode64(attachment['content']))
|
73
|
+
end
|
74
|
+
else
|
75
|
+
params['attachments'].each do |_,attachment|
|
76
|
+
add_file(this.class.add_file_arguments(attachment))
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Extra Cloudmailin parameters. The multipart format uses CRLF whereas
|
83
|
+
# the JSON format uses LF. Normalize to LF.
|
84
|
+
message['reply_plain'] = params['reply_plain'].gsub("\r\n", "\n")
|
85
|
+
message['spf-result'] = params['envelope']['spf']['result']
|
86
|
+
|
87
|
+
[message]
|
88
|
+
else
|
89
|
+
raise ArgumentError, "Can't handle Cloudmailin #{@http_post_format} HTTP POST format"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# @param [Mail::Message] message a message
|
94
|
+
# @return [Boolean] whether the message is spam
|
95
|
+
def spam?(message)
|
96
|
+
message['spf-result'] && message['spf-result'].value == 'fail'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -1,20 +1,28 @@
|
|
1
1
|
module MultiMail
|
2
2
|
module Receiver
|
3
|
+
# Mailgun's incoming email receiver.
|
3
4
|
class Mailgun < MultiMail::Service
|
4
5
|
include MultiMail::Receiver::Base
|
5
6
|
|
6
7
|
requires :mailgun_api_key
|
8
|
+
recognizes :http_post_format
|
7
9
|
|
10
|
+
# Initializes a Mailgun incoming email receiver.
|
11
|
+
#
|
8
12
|
# @param [Hash] options required and optional arguments
|
9
|
-
# @option
|
13
|
+
# @option options [String] :mailgun_api_key a Mailgun API key
|
14
|
+
# @option options [String] :http_post_format "parsed" or "raw"
|
10
15
|
def initialize(options = {})
|
11
16
|
super
|
12
17
|
@mailgun_api_key = options[:mailgun_api_key]
|
18
|
+
@http_post_format = options[:http_post_format]
|
13
19
|
end
|
14
20
|
|
21
|
+
# Returns whether a request originates from Mailgun.
|
22
|
+
#
|
15
23
|
# @param [Hash] params the content of Mailgun's webhook
|
16
24
|
# @return [Boolean] whether the request originates from Mailgun
|
17
|
-
# @
|
25
|
+
# @raise [IndexError] if the request is missing parameters
|
18
26
|
# @see http://documentation.mailgun.net/user_manual.html#securing-webhooks
|
19
27
|
def valid?(params)
|
20
28
|
params.fetch('signature') == OpenSSL::HMAC.hexdigest(
|
@@ -22,53 +30,83 @@ module MultiMail
|
|
22
30
|
'%s%s' % [params.fetch('timestamp'), params.fetch('token')])
|
23
31
|
end
|
24
32
|
|
33
|
+
# Transforms the content of Mailgun's webhook into a list of messages.
|
34
|
+
#
|
25
35
|
# @param [Hash] params the content of Mailgun's webhook
|
26
36
|
# @return [Array<Mail::Message>] messages
|
27
|
-
# @
|
28
|
-
#
|
29
|
-
# @todo parse attachments properly
|
37
|
+
# @see http://documentation.mailgun.net/user_manual.html#mime-messages-parameters
|
38
|
+
# @see http://documentation.mailgun.net/user_manual.html#parsed-messages-parameters
|
30
39
|
def transform(params)
|
31
|
-
|
32
|
-
|
33
|
-
headers
|
34
|
-
|
40
|
+
case @http_post_format
|
41
|
+
when 'parsed', '', nil
|
42
|
+
headers = Multimap.new
|
43
|
+
JSON.parse(params['message-headers']).each do |key,value|
|
44
|
+
headers[key] = value
|
45
|
+
end
|
46
|
+
|
47
|
+
this = self
|
48
|
+
message = Mail.new do
|
49
|
+
headers headers
|
50
|
+
|
51
|
+
# The following are redundant with `body-mime` in raw MIME format
|
52
|
+
# and with `message-headers` in fully parsed format.
|
53
|
+
#
|
54
|
+
# from params['from']
|
55
|
+
# sender params['sender']
|
56
|
+
# to params['recipient']
|
57
|
+
# subject params['subject']
|
58
|
+
#
|
59
|
+
# Mailgun POSTs all MIME headers both individually and in
|
60
|
+
# `message-headers`.
|
35
61
|
|
36
|
-
|
37
|
-
|
62
|
+
text_part do
|
63
|
+
body params['body-plain']
|
64
|
+
end
|
38
65
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
66
|
+
if params.key?('body-html')
|
67
|
+
html_part do
|
68
|
+
content_type 'text/html; charset=UTF-8'
|
69
|
+
body params['body-html']
|
70
|
+
end
|
71
|
+
end
|
45
72
|
|
46
|
-
|
47
|
-
|
73
|
+
if params.key?('attachment-count')
|
74
|
+
1.upto(params['attachment-count'].to_i).each do |n|
|
75
|
+
attachment = params["attachment-#{n}"]
|
76
|
+
add_file(this.class.add_file_arguments(attachment))
|
77
|
+
end
|
78
|
+
end
|
48
79
|
end
|
49
80
|
|
50
|
-
|
51
|
-
|
52
|
-
|
81
|
+
# Extra Mailgun parameters.
|
82
|
+
extra = [
|
83
|
+
'stripped-text',
|
84
|
+
'stripped-signature',
|
85
|
+
'stripped-html',
|
86
|
+
'content-id-map',
|
87
|
+
]
|
88
|
+
|
89
|
+
# Non-plain, non-HTML body parts.
|
90
|
+
extra += params.keys.select do |key|
|
91
|
+
key[/\Abody-(?!html|plain)/]
|
53
92
|
end
|
54
|
-
end
|
55
93
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
'attachment-count',
|
61
|
-
'attachment-x',
|
62
|
-
'content-id-map',
|
63
|
-
].each do |key|
|
64
|
-
if !params[key].nil? && !params[key].empty?
|
65
|
-
message[key] = params[key]
|
94
|
+
extra.each do |key|
|
95
|
+
if params.key?(key) && !params[key].empty?
|
96
|
+
message[key] = params[key]
|
97
|
+
end
|
66
98
|
end
|
67
|
-
end
|
68
99
|
|
69
|
-
|
100
|
+
[message]
|
101
|
+
when 'raw'
|
102
|
+
[Mail.new(params['body-mime'])]
|
103
|
+
else
|
104
|
+
raise ArgumentError, "Can't handle Mailgun #{@http_post_format} HTTP POST format"
|
105
|
+
end
|
70
106
|
end
|
71
107
|
|
108
|
+
# Returns whether a message is spam.
|
109
|
+
#
|
72
110
|
# @param [Mail::Message] message a message
|
73
111
|
# @return [Boolean] whether the message is spam
|
74
112
|
# @see http://documentation.mailgun.net/user_manual.html#spam-filter
|
@@ -77,7 +115,7 @@ module MultiMail
|
|
77
115
|
# @note We may also inspect `X-Mailgun-SScore` and `X-Mailgun-Spf`, whose
|
78
116
|
# possible values are "Pass", "Neutral", "Fail" and "SoftFail".
|
79
117
|
def spam?(message)
|
80
|
-
message['X-Mailgun-Sflag'].value == 'Yes'
|
118
|
+
message['X-Mailgun-Sflag'] && message['X-Mailgun-Sflag'].value == 'Yes'
|
81
119
|
end
|
82
120
|
end
|
83
121
|
end
|