Skip to content

Commit

Permalink
add support for Cloudmailin URL attachments, closes #11
Browse files Browse the repository at this point in the history
  • Loading branch information
James McKinney committed Sep 7, 2013
1 parent 6a87f2e commit e0fe9cc
Show file tree
Hide file tree
Showing 6 changed files with 290 additions and 17 deletions.
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,21 @@ service = MultiMail::Receiver.new({
})
```

If you are using an [Amazon S3 attachment store](http://docs.cloudmailin.com/receiving_email/attachments/), add a `:attachment_store => true` option. You must set the attachment store's permission setting to "Public Read". Note that attachment stores work with the `multipart` and `json` HTTP POST formats only.

```ruby
service = MultiMail::Receiver.new({
:provider => 'cloudmailin',
:http_post_format => 'multipart',
:attachment_store => true,
})
```

See [Cloudmailin's documentation](http://docs.cloudmailin.com/http_post_formats/) for these additional parameters provided by the API:

* `reply_plain`
* `spf-result`

**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 default `raw` format).

## Mailgun

### Incoming
Expand Down
21 changes: 18 additions & 3 deletions lib/multi_mail/cloudmailin/receiver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@ module Receiver
class Cloudmailin
include MultiMail::Receiver::Base

recognizes :http_post_format
recognizes :http_post_format, :attachment_store

# Initializes a Cloudmailin incoming email receiver.
#
# @param [Hash] options required and optional arguments
# @option options [String] :http_post_format "multipart", "json" or "raw"
# @option options [Boolean] :attachment_store whether attachments have
# been sent to an attachment store
def initialize(options = {})
super
@http_post_format = options[:http_post_format]
@attachment_store = options[:attachment_store]
end

# @param [Hash] params the content of Cloudmailin's webhook
Expand All @@ -40,6 +43,7 @@ def transform(params)
# Mail changes `self`.
headers = self.class.multimap(params['headers'])
http_post_format = @http_post_format
attachment_store = @attachment_store
this = self

message = Mail.new do
Expand All @@ -57,13 +61,24 @@ def transform(params)
end

if params.key?('attachments')
# Using something like lazy.rb will not prevent the HTTP request,
# because the Mail gem must be able to call #valid_encoding? on
# the attachment body (in Ruby 1.9).
if http_post_format == 'json'
params['attachments'].each do |attachment|
add_file(:filename => attachment['file_name'], :content => Base64.decode64(attachment['content']))
if attachment_store
add_file(:filename => attachment['file_name'], :content => Faraday.get(attachment['url']).body)
else
add_file(:filename => attachment['file_name'], :content => Base64.decode64(attachment['content']))
end
end
else
params['attachments'].each do |_,attachment|
add_file(this.class.add_file_arguments(attachment))
if attachment_store
add_file(:filename => attachment['file_name'], :content => Faraday.get(attachment['url']).body)
else
add_file(this.class.add_file_arguments(attachment))
end
end
end
end
Expand Down
23 changes: 18 additions & 5 deletions spec/cloudmailin/receiver_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,20 @@ def params(fixture)

describe '#transform' do
it 'should return a mail message' do
messages = service.transform(params('valid'))
helper(service.transform(params('valid')))
end

it 'should return a mail message with URL attachments' do
if %w(json multipart).include?(http_post_format)
helper(MultiMail::Receiver.new({
:provider => :cloudmailin,
:http_post_format => http_post_format,
:attachment_store => true,
}).transform(params('attachment_store')), true)
end
end

def helper(messages, attachment_store = false)
messages.size.should == 1
message = messages[0]

Expand All @@ -60,8 +73,8 @@ def params(fixture)
text_part.body.decoded.should == "bold text\n\n\n\nsome more bold text\n\n\n\nsome italic text\n\n> multiline\n> quoted\n> text\n\n\n--\nSignature block"

# @note Due to a Cloudmailin bug, the HTML part is missing content
# unless you use the "raw" HTTP POST format.
if actual_http_post_format == 'raw'
# unless you use the "raw" HTTP POST format or URL attachments.
if actual_http_post_format == 'raw' || attachment_store
html_part.body.decoded.should == %(<html><head></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><b>bold text</b><div><br></div><div></div></body></html><html><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><head></head><br><div></div><div><br></div><div><b>some more bold text</b></div><div><b><br></b></div><div><b></b></div></body></html><html><head></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><br><div><b></b></div><div><b><span class="Apple-style-span" style="font-weight: normal; "><br></span></b></div><div><b><span class="Apple-style-span" style="font-weight: normal; "><i>some italic text</i></span></b></div><div><b><span class="Apple-style-span" style="font-weight: normal; "><br></span></b></div><div><blockquote type="cite">multiline</blockquote><blockquote type="cite">quoted</blockquote><blockquote type="cite">text</blockquote></div><div><br></div><div>--</div><div>Signature block</div></body></html>)
else
html_part.body.decoded.should == %(<html><head></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><b>bold text</b><div><br></div><div></div></body></html>)
Expand All @@ -71,8 +84,8 @@ def params(fixture)
attachment0 = message.attachments.find{|attachment| attachment.filename == 'foo.txt'}
attachment1 = message.attachments.find{|attachment| attachment.filename == 'bar.txt'}
# @note Cloudmailin removes the newline at the end of the file,
# unless you use the "raw" HTTP POST format.
if actual_http_post_format == 'raw'
# unless you use the "raw" HTTP POST format or URL attachments.
if actual_http_post_format == 'raw' || attachment_store
attachment0.read.should == "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n"
attachment1.read.should == "Nam accumsan euismod eros et rhoncus.\n"
else
Expand Down
65 changes: 65 additions & 0 deletions spec/fixtures/cloudmailin/json/attachment_store.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
HTTP/1.1 200 OK
Content-Length: 3798
Content-Type: application/json
X-Request-Start: 1378523197697
X-Forwarded-Proto: http
X-Forwarded-Port: 80
X-Forwarded-For: 174.129.35.193
User-Agent: CloudMailin Server
Host: rackbin.herokuapp.com
Connection: close
Version: HTTP/1.1

{
"headers": {
"Return-Path": "james@opennorth.ca",
"Received": [
"by mail-qc0-f177.google.com with SMTP id x12so1548824qcv.36 for <5dae6f85cd65d30d384a@cloudmailin.net>; Fri, 06 Sep 2013 20:06:37 -0700",
"from [192.168.2.20] ([70.49.74.123]) by mx.google.com with ESMTPSA id fy7sm2646347qeb.1.1969.12.31.16.00.00 (version=TLSv1 cipher=RC4-SHA bits=128/128); Fri, 06 Sep 2013 20:06:36 -0700"
],
"Date": "Mon, 15 Apr 2013 20:20:12 -0400",
"From": "James McKinney <james@opennorth.ca>",
"To": "5dae6f85cd65d30d384a@cloudmailin.net",
"Message-ID": "<D3F16515-ED8C-4DEC-80EA-553B7266FD4C@opennorth.ca>",
"Subject": "Test",
"Mime-Version": "1.0",
"Content-Type": "multipart/alternative; boundary=\"Apple-Mail=_58AF8BB6-04D5-4B22-95D5-BFFB183CB821\"; charset=UTF-8",
"Content-Transfer-Encoding": "7bit",
"X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:content-type:subject:date:message-id:to :mime-version; bh=XFFIu2nTnDfhViW8rylqgBXliiVE9n99uIZDjHX7vN8=; b=BtYVZz6pmZ6P7A4lqf64efvrOa4WSGa5UHybTnd7IG3dwneGEnz4QaH/nCdzBrzdKT 8ukGjPp+b3mx3S8TrXrX98/9c6Q1mVgFMBwYtL7NNnfoLmAEvgB9tgitPHgb6/q/N2bm oJNV7XQ9P07Snfq1NJcNNOk8FiJVYdYerUceMN6A8nIXnyhfWGOse+TGLQVMpxJ4yT74 gOadKvaQpumsjZmPpyTViv7rTGXSkTsmxleb0gAJxabFrUFDcp0kptY8pHEwek7Qi+/j 1h3HmTh3F9WmC0/EJbKyEXQTbujtDzsAzWBmov4uwh2885bgWwUzXDJc+JrsI0qX0TQw IrNQ==",
"X-Gm-Message-State": "ALoCoQkL/+YuGYWfMRnoFzhV8wFry1fgjjK7/X7lmXUXSXYJcQL91b9WlM0+gLt+zU2m2yKg/Io2",
"X-Received": "by 10.224.172.131 with SMTP id l3mr7166098qaz.35.1378523197028; Fri, 06 Sep 2013 20:06:37 -0700 (PDT)",
"X-Mailer": "Apple Mail (2.1283)"
},
"envelope": {
"to": "5dae6f85cd65d30d384a@cloudmailin.net",
"recipients": [
"5dae6f85cd65d30d384a@cloudmailin.net"
],
"from": "james@opennorth.ca",
"helo_domain": "mail-qc0-f177.google.com",
"remote_ip": "209.85.216.177",
"spf": {
"result": "pass",
"domain": "opennorth.ca"
}
},
"plain": "bold text\n\n\n\nsome more bold text\n\n\n\nsome italic text\n\n> multiline\n> quoted\n> text\n\n\n--\nSignature block",
"html": "<html><head></head><body style=\"word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; \"><b>bold text</b><div><br></div><div></div></body></html><html><body style=\"word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; \"><head></head><br><div></div><div><br></div><div><b>some more bold text</b></div><div><b><br></b></div><div><b></b></div></body></html><html><head></head><body style=\"word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; \"><br><div><b></b></div><div><b><span class=\"Apple-style-span\" style=\"font-weight: normal; \"><br></span></b></div><div><b><span class=\"Apple-style-span\" style=\"font-weight: normal; \"><i>some italic text</i></span></b></div><div><b><span class=\"Apple-style-span\" style=\"font-weight: normal; \"><br></span></b></div><div><blockquote type=\"cite\">multiline</blockquote><blockquote type=\"cite\">quoted</blockquote><blockquote type=\"cite\">text</blockquote></div><div><br></div><div>--</div><div>Signature block</div></body></html>",
"reply_plain": "bold text\n\n\n\nsome more bold text\n\n\n\nsome italic text\n",
"attachments": [
{
"file_name": "foo.txt",
"content_type": "text/plain",
"size": "57",
"disposition": "attachment",
"url": "http://multi-mail.s3.amazonaws.com/5e2dd992326d08ac5c72.txt"
},
{
"file_name": "bar.txt",
"content_type": "text/plain",
"size": "38",
"disposition": "attachment",
"url": "http://multi-mail.s3.amazonaws.com/b296959bbe7889c172c8.txt"
}
]
}
174 changes: 174 additions & 0 deletions spec/fixtures/cloudmailin/multipart/attachment_store.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
HTTP/1.1 200 OK
Content-Length: 5886
Content-Type: multipart/form-data; boundary=----cloudmailinboundry
X-Request-Start: 1378525227186
X-Forwarded-Proto: http
X-Forwarded-Port: 80
X-Forwarded-For: 109.107.35.53
User-Agent: CloudMailin Server
Host: rackbin.herokuapp.com
Connection: close
Version: HTTP/1.1

------cloudmailinboundry
Content-Disposition: form-data; name="plain"

bold text



some more bold text



some italic text

> multiline
> quoted
> text


--
Signature block
------cloudmailinboundry
Content-Disposition: form-data; name="html"

<html><head></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><b>bold text</b><div><br></div><div></div></body></html><html><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><head></head><br><div></div><div><br></div><div><b>some more bold text</b></div><div><b><br></b></div><div><b></b></div></body></html><html><head></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><br><div><b></b></div><div><b><span class="Apple-style-span" style="font-weight: normal; "><br></span></b></div><div><b><span class="Apple-style-span" style="font-weight: normal; "><i>some italic text</i></span></b></div><div><b><span class="Apple-style-span" style="font-weight: normal; "><br></span></b></div><div><blockquote type="cite">multiline</blockquote><blockquote type="cite">quoted</blockquote><blockquote type="cite">text</blockquote></div><div><br></div><div>--</div><div>Signature block</div></body></html>
------cloudmailinboundry
Content-Disposition: form-data; name="reply_plain"

bold text



some more bold text



some italic text

------cloudmailinboundry
Content-Disposition: form-data; name="headers[Return-Path]"

james@opennorth.ca
------cloudmailinboundry
Content-Disposition: form-data; name="headers[Received][0]"

by mail-qc0-f175.google.com with SMTP id v2so2167362qcr.34 for <5dae6f85cd65d30d384a@cloudmailin.net>; Fri, 06 Sep 2013 20:40:24 -0700
------cloudmailinboundry
Content-Disposition: form-data; name="headers[Received][1]"

from [192.168.2.20] ([70.49.74.123]) by mx.google.com with ESMTPSA id u9sm2845233qar.4.1969.12.31.16.00.00 (version=TLSv1 cipher=RC4-SHA bits=128/128); Fri, 06 Sep 2013 20:40:23 -0700
------cloudmailinboundry
Content-Disposition: form-data; name="headers[Date]"

Mon, 15 Apr 2013 20:20:12 -0400
------cloudmailinboundry
Content-Disposition: form-data; name="headers[From]"

James McKinney <james@opennorth.ca>
------cloudmailinboundry
Content-Disposition: form-data; name="headers[To]"

5dae6f85cd65d30d384a@cloudmailin.net
------cloudmailinboundry
Content-Disposition: form-data; name="headers[Message-ID]"

<6021B546-125D-45E4-8619-7D1EC6529E3F@opennorth.ca>
------cloudmailinboundry
Content-Disposition: form-data; name="headers[Subject]"

Test
------cloudmailinboundry
Content-Disposition: form-data; name="headers[Mime-Version]"

1.0
------cloudmailinboundry
Content-Disposition: form-data; name="headers[Content-Transfer-Encoding]"

7bit
------cloudmailinboundry
Content-Disposition: form-data; name="headers[X-Google-DKIM-Signature]"

v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:content-type:subject:date:message-id:to :mime-version; bh=7QXxeoo2eFC71SO2+2Aan6OBpxRp1CgvZJtniYo2Vfs=; b=hUEOWvxIUI4/bEhDZJfcgaxEixsboqL/ffXpWQwiy9lLNvsNiDJjtUzQ8fZbaAptkf DMzHDJcXyWtJP/FvCIbq5pnxpmzCDbua3FEx0oA8OxgbBjraStm6Qri781LxJZJMC+jA C6g0D6wMFSwLv4L+nWywOXnfM4d5oEb4mMJHbUSh8zx8SHHehMBDUW++rRZTUL9ful88 0sLNKvIBJD3+plw9sLtYwW6PfTRr9X4YwFr0e9RHqyOlC0dmoNcbGyuuynM86D9BIi2p yK4YL9Y0+8ZL+zg/XrtxJlQb+XSo6x+GmEsaCRaXgSdVvmFdMsIndL/iqQH9B3LiDjYy 87iw==
------cloudmailinboundry
Content-Disposition: form-data; name="headers[X-Gm-Message-State]"

ALoCoQkbW3nfV+/NutlB/vrs+q4iTk6ADjnuiL05fzCXtJOgp1dHetP+C2v68SEz1jqqJUD5tVGy
------cloudmailinboundry
Content-Disposition: form-data; name="headers[X-Received]"

by 10.49.116.4 with SMTP id js4mr6948310qeb.14.1378525224314; Fri, 06 Sep 2013 20:40:24 -0700 (PDT)
------cloudmailinboundry
Content-Disposition: form-data; name="headers[X-Mailer]"

Apple Mail (2.1283)
------cloudmailinboundry
Content-Disposition: form-data; name="envelope[to]"

5dae6f85cd65d30d384a@cloudmailin.net
------cloudmailinboundry
Content-Disposition: form-data; name="envelope[recipients][0]"

5dae6f85cd65d30d384a@cloudmailin.net
------cloudmailinboundry
Content-Disposition: form-data; name="envelope[from]"

james@opennorth.ca
------cloudmailinboundry
Content-Disposition: form-data; name="envelope[helo_domain]"

mail-qc0-f175.google.com
------cloudmailinboundry
Content-Disposition: form-data; name="envelope[remote_ip]"

209.85.216.175
------cloudmailinboundry
Content-Disposition: form-data; name="envelope[spf][result]"

pass
------cloudmailinboundry
Content-Disposition: form-data; name="envelope[spf][domain]"

opennorth.ca
------cloudmailinboundry
Content-Disposition: form-data; name="attachments[0][file_name]"

foo.txt
------cloudmailinboundry
Content-Disposition: form-data; name="attachments[0][content_type]"

text/plain
------cloudmailinboundry
Content-Disposition: form-data; name="attachments[0][size]"

57
------cloudmailinboundry
Content-Disposition: form-data; name="attachments[0][disposition]"

attachment
------cloudmailinboundry
Content-Disposition: form-data; name="attachments[0][url]"

http://multi-mail.s3.amazonaws.com/77a3b3e1ca79c38dbba8.txt
------cloudmailinboundry
Content-Disposition: form-data; name="attachments[1][file_name]"

bar.txt
------cloudmailinboundry
Content-Disposition: form-data; name="attachments[1][content_type]"

text/plain
------cloudmailinboundry
Content-Disposition: form-data; name="attachments[1][size]"

38
------cloudmailinboundry
Content-Disposition: form-data; name="attachments[1][disposition]"

attachment
------cloudmailinboundry
Content-Disposition: form-data; name="attachments[1][url]"

http://multi-mail.s3.amazonaws.com/40078e93b6340a97ac6c.txt
------cloudmailinboundry--
12 changes: 5 additions & 7 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@
# in spec/support/ and its subdirectories.
Dir[File.expand_path("../support/**/*.rb", __FILE__)].each {|f| require f}

# Use requestb.in. Copy the content from the "Raw" tab and replace the first
# line with "HTTP/1.1 200 OK". Note that messages cannot exceed 10kb. All
# fixtures are modified to have the same Date header.
# Use Rackbin. Prepend "HTTP/1.1 200 OK" to the request. All fixtures are
# modified to have the same Date header.
#
# If you see `bad content body` exceptions, run `unix2dos` on the fixtures.
#
# Sign up for all services, and, in all cases except Cloudmailin, add an API key
# to `api_keys.yml`, which will look like:
Expand All @@ -43,12 +44,9 @@
#
# For Postmark, you must create a server to get an API key.
#
# If you see `bad content body` exceptions, run `unix2dos` on the fixtures.
#
# # Cloudmailin
#
# Change the HTTP POST format on Cloudmailin and wait a few minutes. Run
# `unix2dos` on the fixtures to fix line endings.
# Change the HTTP POST format on Cloudmailin and wait up to a few minutes.
#
# spam.txt Change the SPF result to "fail"
# valid.txt Send a complex multipart message
Expand Down

0 comments on commit e0fe9cc

Please sign in to comment.