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.
- 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
|
+
[![Build Status](https://secure.travis-ci.org/opennorth/multi_mail.png)](http://travis-ci.org/opennorth/multi_mail)
|
3
4
|
[![Dependency Status](https://gemnasium.com/opennorth/multi_mail.png)](https://gemnasium.com/opennorth/multi_mail)
|
4
|
-
[![
|
5
|
+
[![Coverage Status](https://coveralls.io/repos/opennorth/multi_mail/badge.png?branch=master)](https://coveralls.io/r/opennorth/multi_mail)
|
6
|
+
[![Code Climate](https://codeclimate.com/github/opennorth/multi_mail.png)](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
|