Skip to content

Commit 95fe46e

Browse files
author
Hernan Schmidt
committed
Add an adapter to use Griddler with Mandrill.
1 parent 74a0849 commit 95fe46e

File tree

9 files changed

+272
-15
lines changed

9 files changed

+272
-15
lines changed

README.md

+24-3
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ Griddler
88

99
Griddler is a Rails engine (full plugin) that provides an endpoint for the
1010
[SendGrid parse api](http://sendgrid.com/docs/API%20Reference/Webhooks/parse.html),
11-
[Cloudmailin parse api](http://cloudmailin.com) or
12-
[Postmark parse api](http://developer.postmarkapp.com/developer-inbound-parse.html)
11+
[Cloudmailin parse api](http://cloudmailin.com),
12+
[Postmark parse api](http://developer.postmarkapp.com/developer-inbound-parse.html) or
13+
[Mandrill parse api](http://help.mandrill.com/entries/21699367-Inbound-Email-Processing-Overview)
1314
that hands off a built email object to a class implemented by you.
1415

1516
Tutorials
@@ -113,7 +114,7 @@ end
113114
the email object. `:hash` will return all options within a -- (surprise!) -- hash.
114115
* `config.email_service` tells Griddler which email service you are using. The supported
115116
email service options are `:sendgrid` (the default), `:cloudmailin` (expects
116-
multipart format) and `:postmark`.
117+
multipart format), `:postmark` and `:mandrill`.
117118

118119
Testing In Your App
119120
-------------------
@@ -200,6 +201,24 @@ def pick_meaningful_recipient(recipients)
200201
end
201202
```
202203

204+
Using Griddler with Mandrill
205+
----------------------------
206+
207+
When adding a webhook in their administration panel, Mandrill will issue a HEAD
208+
request to check if the webhook is valid (see
209+
[Adding Routes](http://help.mandrill.com/entries/21699367-Inbound-Email-Processing-Overview)).
210+
If the HEAD request fails, Mandrill will not allow you to add the webhook.
211+
Since Griddler is only configured to handle POST requests, you will not be able
212+
to add the webhook as-is. To solve this, add a temporary route to your
213+
application that can handle the HEAD request:
214+
215+
```ruby
216+
# routes.rb
217+
get '/email_processor', :to => proc { [200, {}, ["OK"]] }
218+
```
219+
220+
Once you have correctly configured Mandrill, you can go ahead and delete this code.
221+
203222
More Information
204223
----------------
205224

@@ -209,6 +228,8 @@ More Information
209228
* [Cloudmailin Docs](http://docs.cloudmailin.com/)
210229
* [Postmark](http://postmarkapp.com)
211230
* [Postmark Docs](http://developer.postmarkapp.com/)
231+
* [Mandrill](http://mandrill.com)
232+
* [Mandrill Docs](http://help.mandrill.com/forums/21092258-Inbound-Email-Processing)
212233

213234
Credits
214235
-------
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
class Griddler::EmailsController < ActionController::Base
22
def create
3-
Griddler::Email.new(normalized_params).process
3+
normalized_params.each do |p|
4+
Griddler::Email.new(p).process
5+
end
46
head :ok
57
end
68

79
private
810

911
def normalized_params
10-
Griddler.configuration.email_service.normalize_params(params)
12+
Array.wrap(Griddler.configuration.email_service.normalize_params(params))
1113
end
1214
end

lib/griddler.rb

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
require 'griddler/adapters/sendgrid_adapter'
99
require 'griddler/adapters/cloudmailin_adapter'
1010
require 'griddler/adapters/postmark_adapter'
11+
require 'griddler/adapters/mandrill_adapter'
1112

1213
module Griddler
1314
end
+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
module Griddler
2+
module Adapters
3+
class MandrillAdapter
4+
def initialize(params)
5+
@params = params
6+
end
7+
8+
def self.normalize_params(params)
9+
adapter = new(params)
10+
adapter.normalize_params
11+
end
12+
13+
def normalize_params
14+
events.map do |event|
15+
{
16+
to: recipients(event),
17+
from: event[:from_email],
18+
subject: event[:subject],
19+
text: event[:text],
20+
html: event[:html],
21+
raw_body: event[:raw_msg],
22+
attachments: attachment_files(event)
23+
}
24+
end
25+
end
26+
27+
private
28+
29+
attr_reader :params
30+
31+
def events
32+
@events ||= ActiveSupport::JSON.decode(params[:mandrill_events]).map do |event|
33+
event['msg'].with_indifferent_access
34+
end
35+
end
36+
37+
def recipients(event)
38+
event[:to].map { |recipient| full_email(recipient) }
39+
end
40+
41+
def full_email(contact_info)
42+
email = contact_info[0]
43+
if contact_info[1]
44+
"#{contact_info[1]} <#{email}>"
45+
else
46+
email
47+
end
48+
end
49+
50+
def attachment_files(event)
51+
attachments = event[:attachments] || Array.new
52+
attachments.map do |key, attachment|
53+
ActionDispatch::Http::UploadedFile.new({
54+
filename: attachment[:name],
55+
type: attachment[:type],
56+
tempfile: create_tempfile(attachment)
57+
})
58+
end
59+
end
60+
61+
def create_tempfile(attachment)
62+
filename = attachment[:name]
63+
tempfile = Tempfile.new(filename, Dir::tmpdir, encoding: 'ascii-8bit')
64+
tempfile.write(Base64.decode64(attachment[:content]))
65+
tempfile.rewind
66+
tempfile
67+
end
68+
end
69+
end
70+
end

lib/griddler/configuration.rb

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def adapter_class
5656
sendgrid: Griddler::Adapters::SendgridAdapter,
5757
cloudmailin: Griddler::Adapters::CloudmailinAdapter,
5858
postmark: Griddler::Adapters::PostmarkAdapter,
59+
mandrill: Griddler::Adapters::MandrillAdapter,
5960
}
6061
end
6162
end

spec/controllers/griddler/emails_controller_spec.rb

-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
end
2020
end
2121

22-
2322
def email_params
2423
{
2524
headers: 'Received: by 127.0.0.1 with SMTP...',

spec/features/adapters_and_email_spec.rb

+22-8
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
require 'spec_helper'
22

33
describe 'Adapters act the same' do
4-
[:sendgrid, :postmark, :cloudmailin].each do |adapter|
4+
[:sendgrid, :postmark, :cloudmailin, :mandrill].each do |adapter|
55
context adapter do
66
it "wraps recipients in an array and passes them to Email by #{adapter}" do
77
Griddler.configuration.email_service = adapter
88

99
normalized_params = Griddler.configuration.email_service.normalize_params(params_for[adapter])
10-
email = Griddler::Email.new(normalized_params)
1110

12-
email.to.should eq([{
13-
token: 'hi',
14-
host: 'example.com',
15-
full: 'Hello World <[email protected]>',
16-
17-
}])
11+
Array.wrap(normalized_params).each do |params|
12+
email = Griddler::Email.new(params)
13+
14+
email.to.should eq([{
15+
token: 'hi',
16+
host: 'example.com',
17+
full: 'Hello World <[email protected]>',
18+
19+
}])
20+
end
21+
1822
end
1923
end
2024
end
@@ -46,5 +50,15 @@ def params_for
4650
to: 'Hello World <[email protected]>',
4751
from: 'There <[email protected]>',
4852
},
53+
mandrill: {
54+
mandrill_events: ActiveSupport::JSON.encode([{
55+
msg: {
56+
text: 'hi',
57+
from_email: "[email protected]",
58+
from_name: "There",
59+
to: [["[email protected]", "Hello World"]],
60+
}
61+
}])
62+
},
4963
}
5064
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
require 'spec_helper'
2+
3+
describe Griddler::Adapters::MandrillAdapter, '.normalize_params' do
4+
it 'normalizes parameters' do
5+
params = default_params
6+
7+
normalized_params = Griddler::Adapters::MandrillAdapter.normalize_params(params)
8+
normalized_params.each do |params|
9+
params[:to].should eq ['The Token <[email protected]>']
10+
params[:from].should eq '[email protected]'
11+
params[:subject].should eq 'hello'
12+
params[:text].should include('Dear bob')
13+
params[:html].should include('<p>Dear bob</p>')
14+
params[:raw_body].should include('raw')
15+
end
16+
end
17+
18+
it 'passes the received array of files' do
19+
params = params_with_attachments
20+
21+
normalized_params = Griddler::Adapters::MandrillAdapter.normalize_params(params)
22+
23+
first, second = *normalized_params[0][:attachments]
24+
25+
first.original_filename.should eq('photo1.jpg')
26+
first.size.should eq(upload_1_params[:length])
27+
28+
second.original_filename.should eq('photo2.jpg')
29+
second.size.should eq(upload_2_params[:length])
30+
end
31+
32+
it 'has no attachments' do
33+
params = default_params
34+
35+
normalized_params = Griddler::Adapters::MandrillAdapter.normalize_params(params)
36+
37+
normalized_params[0][:attachments].should be_empty
38+
end
39+
40+
def default_params
41+
mandrill_events (params_hash*2).to_json
42+
end
43+
44+
def params_hash
45+
[{
46+
event: "inbound",
47+
ts: 1364601140,
48+
msg:
49+
{
50+
raw_msg: "raw",
51+
headers: {},
52+
text: text_body,
53+
html: text_html,
54+
from_email: "[email protected]",
55+
from_name: "Hernan Example",
56+
to: [["[email protected]", "The Token"]],
57+
subject: "hello",
58+
spam_report: {
59+
score: -0.8,
60+
matched_rules: "..."
61+
},
62+
dkim: {signed: true, valid: true},
63+
spf: {result: "pass", detail: "sender SPF authorized"},
64+
65+
tags: [],
66+
sender: nil
67+
}
68+
}]
69+
end
70+
71+
def params_with_attachments
72+
params = params_hash
73+
params[0][:msg][:attachments] = {
74+
'photo1.jpg' => upload_1_params,
75+
'photo2.jpg' => upload_2_params
76+
}
77+
mandrill_events params.to_json
78+
end
79+
80+
def mandrill_events(json)
81+
{ mandrill_events: json }
82+
end
83+
84+
def text_body
85+
<<-EOS.strip_heredoc.strip
86+
Dear bob
87+
88+
Reply ABOVE THIS LINE
89+
90+
hey sup
91+
EOS
92+
end
93+
94+
def text_html
95+
<<-EOS.strip_heredoc.strip
96+
<p>Dear bob</p>
97+
98+
Reply ABOVE THIS LINE
99+
100+
hey sup
101+
EOS
102+
end
103+
104+
def cwd
105+
File.expand_path File.dirname(__FILE__)
106+
end
107+
108+
def upload_1_params
109+
@upload_1_params ||= begin
110+
file = File.new("#{cwd}/../../../spec/fixtures/photo1.jpg")
111+
size = file.size
112+
{
113+
name: 'photo1.jpg',
114+
content: Base64.encode64(file.read),
115+
type: 'image/jpeg',
116+
length: file.size
117+
}
118+
end
119+
end
120+
121+
def upload_2_params
122+
@upload_2_params ||= begin
123+
file = File.new("#{cwd}/../../../spec/fixtures/photo2.jpg")
124+
size = file.size
125+
{
126+
name: 'photo2.jpg',
127+
content: Base64.encode64(file.read),
128+
type: 'image/jpeg',
129+
length: file.size
130+
}
131+
end
132+
end
133+
134+
def upload_1
135+
@upload_1 ||= ActionDispatch::Http::UploadedFile.new({
136+
filename: 'photo1.jpg',
137+
type: 'image/jpeg',
138+
tempfile: File.new("#{cwd}/../../../spec/fixtures/photo1.jpg")
139+
})
140+
end
141+
142+
def upload_2
143+
@upload_2 ||= ActionDispatch::Http::UploadedFile.new({
144+
filename: 'photo2.jpg',
145+
type: 'image/jpeg',
146+
tempfile: File.new("#{cwd}/../../../spec/fixtures/photo2.jpg")
147+
})
148+
end
149+
end

spec/griddler/configuration_spec.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
end
6666

6767
it "accepts all valid email service adapter settings" do
68-
[:sendgrid, :cloudmailin, :postmark].each do |adapter|
68+
[:sendgrid, :cloudmailin, :postmark, :mandrill].each do |adapter|
6969
config = lambda do
7070
Griddler.configure do |c|
7171
c.email_service = adapter

0 commit comments

Comments
 (0)