html-griddler 0.1.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/LICENSE +19 -0
- data/README.md +154 -0
- data/Rakefile +1 -0
- data/app/controllers/griddler/emails_controller.rb +6 -0
- data/config/initializers/griddler.rb +1 -0
- data/config/routes.rb +3 -0
- data/lib/html-griddler/configuration.rb +33 -0
- data/lib/html-griddler/email.rb +97 -0
- data/lib/html-griddler/email_parser.rb +49 -0
- data/lib/html-griddler/engine.rb +4 -0
- data/lib/html-griddler/errors.rb +9 -0
- data/lib/html-griddler/version.rb +3 -0
- data/lib/html-griddler.rb +8 -0
- data/lib/tasks/griddler_tasks.rake +4 -0
- metadata +128 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2012 Caleb Thompson, Joel Oliveira and thoughtbot, inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
Griddler
|
2
|
+
========
|
3
|
+
|
4
|
+
### Receive emails in your Rails app
|
5
|
+
|
6
|
+
Griddler is a Rails engine (full plugin) that provides an endpoint for the
|
7
|
+
[SendGrid parse api](http://sendgrid.com/docs/API%20Reference/Webhooks/parse.html)
|
8
|
+
that hands off a built email object to a class implemented by you.
|
9
|
+
|
10
|
+
Tutorials
|
11
|
+
---------
|
12
|
+
|
13
|
+
* SendGrid has done a
|
14
|
+
[great tutorial](http://blog.sendgrid.com/receiving-email-in-your-rails-app-with-griddler/)
|
15
|
+
on integrating Griddler with your application.
|
16
|
+
* And of course, view our own blog post on the subject over at
|
17
|
+
[Giant Robots](http://robots.thoughtbot.com/post/42286882447/handle-incoming-email-with-griddler).
|
18
|
+
|
19
|
+
Installation
|
20
|
+
------------
|
21
|
+
|
22
|
+
Add griddler to your application's Gemfile and run `bundle install`:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
gem 'griddler'
|
26
|
+
```
|
27
|
+
|
28
|
+
Griddler comes with a default endpoint that will be displayed at the bottom
|
29
|
+
of the output of `rake routes`. If there is a previously defined route that
|
30
|
+
matches `/email_processor` -- or you would like to rename the matched path -- you
|
31
|
+
may add the route to the desired position in routes.rb with the following:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
post '/email_processor' => 'griddler/emails#create'
|
35
|
+
```
|
36
|
+
|
37
|
+
Defaults
|
38
|
+
--------
|
39
|
+
|
40
|
+
By default Griddler will look for a class to be created in your application
|
41
|
+
called EmailProcessor with a class method implemented, named process, taking
|
42
|
+
in one argument (presumably `email`). For example, in `./lib/email_processor.rb`:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
class EmailProcessor
|
46
|
+
def self.process(email)
|
47
|
+
# all of your application-specific code here - creating models,
|
48
|
+
# processing reports, etc
|
49
|
+
end
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
The contents of the `email` object passed into your process method is an object
|
54
|
+
that responds to:
|
55
|
+
|
56
|
+
* `.to`
|
57
|
+
* `.from`
|
58
|
+
* `.subject`
|
59
|
+
* `.body`
|
60
|
+
* `.raw_body`
|
61
|
+
|
62
|
+
Each of those has some sensible defaults.
|
63
|
+
|
64
|
+
`.from`, `.raw_body` and `.subject` will contain the obvious values found in the email, the raw values from those fields.
|
65
|
+
|
66
|
+
`.body` will contain the full contents of the email body **unless** there is a
|
67
|
+
line in the email containing the string `-- Reply ABOVE THIS LINE --`. In that
|
68
|
+
case `.body` will contain everything before that line.
|
69
|
+
|
70
|
+
`.to` will contain all of the text before the email's "@" character. We've found
|
71
|
+
that this is the most often used portion of the email address and consider it to
|
72
|
+
be the token we'll key off of for interaction with our application.
|
73
|
+
|
74
|
+
Configuration Options
|
75
|
+
---------------------
|
76
|
+
|
77
|
+
An initializer can be created to control some of the options in Griddler. Defaults
|
78
|
+
are shown below with sample overrides following. In `config/initializer/griddler.rb`:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
Griddler.configure do |config|
|
82
|
+
config.processor_class = EmailProcessor # MyEmailProcessor
|
83
|
+
config.to = :token # :raw, :email, :hash
|
84
|
+
# :raw => 'AppName <s13.6b2d13dc6a1d33db7644@mail.myapp.com>'
|
85
|
+
# :email => 's13.6b2d13dc6a1d33db7644@mail.myapp.com'
|
86
|
+
# :token => 's13.6b2d13dc6a1d33db7644'
|
87
|
+
# :hash => { raw: '', email: '', token: '', host: '' }
|
88
|
+
config.reply_delimiter = '-- REPLY ABOVE THIS LINE --'
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
* `config.processor_class` is the class Griddler will use to handle your incoming emails.
|
93
|
+
* `config.reply_delimiter` is the string searched for that will split your body.
|
94
|
+
* `config.to` is the format of the returned value for the `:to` key in
|
95
|
+
the email object. `:hash` will return all options within a -- (surprise!) -- hash.
|
96
|
+
|
97
|
+
Testing In Your App
|
98
|
+
-------------------
|
99
|
+
|
100
|
+
You may want to create a factory for when testing the integration of Griddler into
|
101
|
+
your application. If you're using factory_girl this can be accomplished with the
|
102
|
+
following sample factory.
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
factory :email, class: OpenStruct do
|
106
|
+
to 'email-token'
|
107
|
+
from 'user@email.com'
|
108
|
+
subject 'email subject'
|
109
|
+
body 'Hello!'
|
110
|
+
attachments {[]}
|
111
|
+
|
112
|
+
trait :with_attachment do
|
113
|
+
attachments {[
|
114
|
+
ActionDispatch::Http::UploadedFile.new({
|
115
|
+
filename: 'img.png',
|
116
|
+
type: 'image/png',
|
117
|
+
tempfile: File.new("#{File.expand_path File.dirname(__FILE__)}/fixtures/img.png")
|
118
|
+
})
|
119
|
+
]}
|
120
|
+
end
|
121
|
+
end
|
122
|
+
```
|
123
|
+
|
124
|
+
Bear in mind, if you plan on using the `:with_attachment` trait, that this
|
125
|
+
example assumes your factories are in `spec/factories.rb` and you have
|
126
|
+
an image file in `spec/fixtures/`.
|
127
|
+
|
128
|
+
To use it in your test(s) just build with `email = build(:email)`
|
129
|
+
or `email = build(:email, :with_attachment)`
|
130
|
+
|
131
|
+
More Information
|
132
|
+
----------------
|
133
|
+
|
134
|
+
* [SendGrid](http://www.sendgrid.com)
|
135
|
+
* [SendGrid Parse API](http://www.sendgrid.com/docs/API Reference/Webhooks/parse.html)
|
136
|
+
|
137
|
+
Credits
|
138
|
+
-------
|
139
|
+
|
140
|
+
Griddler was written by Caleb Thompson and Joel Oliveira.
|
141
|
+
|
142
|
+
Large portions of the codebase were extracted from thoughtbot's
|
143
|
+
[Trajectory](http://www.apptrajectory.com).
|
144
|
+
|
145
|
+

|
146
|
+
|
147
|
+
The names and logos for thoughtbot are trademarks of thoughtbot, inc.
|
148
|
+
|
149
|
+
License
|
150
|
+
-------
|
151
|
+
|
152
|
+
Griddler is Copyright © 2012 Caleb Thompson, Joel Oliveira and thoughtbot. It is
|
153
|
+
free software, and may be redistributed under the terms specified in the LICENSE
|
154
|
+
file.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'griddler'
|
data/config/routes.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module Griddler
|
2
|
+
@@configuration = nil
|
3
|
+
|
4
|
+
def self.configure
|
5
|
+
@@configuration = Configuration.new
|
6
|
+
|
7
|
+
if block_given?
|
8
|
+
yield configuration
|
9
|
+
end
|
10
|
+
|
11
|
+
configuration
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.configuration
|
15
|
+
@@configuration || configure
|
16
|
+
end
|
17
|
+
|
18
|
+
class Configuration
|
19
|
+
attr_accessor :processor_class, :reply_delimiter, :to
|
20
|
+
|
21
|
+
def to
|
22
|
+
@to ||= :token
|
23
|
+
end
|
24
|
+
|
25
|
+
def processor_class
|
26
|
+
@processor_class ||= EmailProcessor
|
27
|
+
end
|
28
|
+
|
29
|
+
def reply_delimiter
|
30
|
+
@reply_delimiter ||= 'Reply ABOVE THIS LINE'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'htmlentities'
|
2
|
+
|
3
|
+
class Griddler::Email
|
4
|
+
include ActionView::Helpers::SanitizeHelper
|
5
|
+
attr_reader :to, :from, :body, :raw_body, :subject, :attachments, :spam_score, :spam_report, :spf, :dkim
|
6
|
+
|
7
|
+
def initialize(params)
|
8
|
+
@params = params
|
9
|
+
@to = extract_address(params[:to], config.to)
|
10
|
+
@from = extract_address(params[:from], :email)
|
11
|
+
@subject = params[:subject]
|
12
|
+
@body = extract_body
|
13
|
+
@raw_body = params[:html] || params[:text]
|
14
|
+
@attachments = extract_attachments
|
15
|
+
@spam_score = params[:spam_score]
|
16
|
+
@spam_report = params[:spam_report]
|
17
|
+
@spf = params[:SPF]
|
18
|
+
@dkim = params[:dkim]
|
19
|
+
end
|
20
|
+
|
21
|
+
def process
|
22
|
+
processor_class = config.processor_class
|
23
|
+
processor_class.process(self)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :params
|
29
|
+
|
30
|
+
def config
|
31
|
+
Griddler.configuration
|
32
|
+
end
|
33
|
+
|
34
|
+
def extract_address(address, type)
|
35
|
+
parsed = EmailParser.parse_address(address)
|
36
|
+
|
37
|
+
if type == :hash
|
38
|
+
parsed
|
39
|
+
else
|
40
|
+
parsed[type]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def extract_attachments
|
45
|
+
attachment_count = params[:attachments].to_i
|
46
|
+
|
47
|
+
attachment_count.times.map do |index|
|
48
|
+
params["attachment#{index + 1}".to_sym]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def extract_body
|
53
|
+
body_text = text_or_sanitized_html
|
54
|
+
|
55
|
+
if params[:charsets].present?
|
56
|
+
body_text = body_text.encode(
|
57
|
+
'UTF-8',
|
58
|
+
invalid: :replace,
|
59
|
+
undef: :replace,
|
60
|
+
replace: ''
|
61
|
+
).force_encoding('UTF-8')
|
62
|
+
end
|
63
|
+
|
64
|
+
EmailParser.extract_reply_body(body_text)
|
65
|
+
end
|
66
|
+
|
67
|
+
def text_or_sanitized_html
|
68
|
+
if params.key? :html
|
69
|
+
clean_html(params[:html])
|
70
|
+
elsif params.key? :text
|
71
|
+
clean_html(params[:text])
|
72
|
+
else
|
73
|
+
raise Griddler::Errors::EmailBodyNotFound
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def clean_text(text)
|
78
|
+
clean_invalid_utf8_bytes(text)
|
79
|
+
end
|
80
|
+
|
81
|
+
def clean_html(html)
|
82
|
+
cleaned_html = clean_invalid_utf8_bytes(html)
|
83
|
+
cleaned_html = strip_tags(cleaned_html)
|
84
|
+
cleaned_html = HTMLEntities.new.decode(cleaned_html)
|
85
|
+
cleaned_html
|
86
|
+
end
|
87
|
+
|
88
|
+
def clean_invalid_utf8_bytes(text)
|
89
|
+
text.encode(
|
90
|
+
'UTF-8',
|
91
|
+
'binary',
|
92
|
+
invalid: :replace,
|
93
|
+
undef: :replace,
|
94
|
+
replace: ''
|
95
|
+
)
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Parse emails from their full format into a hash containing full email, host,
|
2
|
+
# local token, and the raw argument.
|
3
|
+
#
|
4
|
+
# Some Body <somebody@example.com>
|
5
|
+
# # => {
|
6
|
+
# token: 'somebody',
|
7
|
+
# host: 'example.com',
|
8
|
+
# email: 'somebody@example.com',
|
9
|
+
# full: 'Some Body <somebody@example.com>',
|
10
|
+
# }
|
11
|
+
module EmailParser
|
12
|
+
def self.parse_address(full_address)
|
13
|
+
email_address = extract_email_address(full_address)
|
14
|
+
token, host = split_address(email_address)
|
15
|
+
{
|
16
|
+
token: token,
|
17
|
+
host: host,
|
18
|
+
email: email_address,
|
19
|
+
full: full_address,
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.extract_reply_body(body)
|
24
|
+
if body
|
25
|
+
delimeter = Griddler.configuration.reply_delimiter
|
26
|
+
body.split(delimeter).first.
|
27
|
+
split(/^\s*[-]+\s*Original Message\s*[-]+\s*$/).first.
|
28
|
+
split(/^\s*--\s*$/).first.
|
29
|
+
gsub(/On.*wrote:/, '').
|
30
|
+
split(/[\r]*\n/).reject do |line|
|
31
|
+
line =~ /^\s*>/ ||
|
32
|
+
line =~ /^\s*Sent from my /
|
33
|
+
end.
|
34
|
+
join("\n").
|
35
|
+
gsub(/^\s*On.*\r?\n?\s*.*\s*wrote:$/,'').
|
36
|
+
strip
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def self.extract_email_address(full_address)
|
43
|
+
full_address.split('<').last.delete('>').strip
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.split_address(email_address)
|
47
|
+
email_address.try :split, '@'
|
48
|
+
end
|
49
|
+
end
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: html-griddler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.2
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Sean Powell - forker :)
|
9
|
+
- Original Authors
|
10
|
+
- Caleb Thompson
|
11
|
+
- Joel Oliveira
|
12
|
+
- thoughtbot
|
13
|
+
- Swift
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2013-02-20 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rails
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: 3.2.0
|
29
|
+
type: :runtime
|
30
|
+
version_requirements: *id001
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: htmlentities
|
33
|
+
prerelease: false
|
34
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: "0"
|
40
|
+
type: :runtime
|
41
|
+
version_requirements: *id002
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rspec-rails
|
44
|
+
prerelease: false
|
45
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: "0"
|
51
|
+
type: :development
|
52
|
+
version_requirements: *id003
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: sqlite3
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "0"
|
62
|
+
type: :development
|
63
|
+
version_requirements: *id004
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
name: jquery-rails
|
66
|
+
prerelease: false
|
67
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: "0"
|
73
|
+
type: :development
|
74
|
+
version_requirements: *id005
|
75
|
+
description:
|
76
|
+
email:
|
77
|
+
- sean@engageyourcause.com
|
78
|
+
executables: []
|
79
|
+
|
80
|
+
extensions: []
|
81
|
+
|
82
|
+
extra_rdoc_files: []
|
83
|
+
|
84
|
+
files:
|
85
|
+
- app/controllers/griddler/emails_controller.rb
|
86
|
+
- config/initializers/griddler.rb
|
87
|
+
- config/routes.rb
|
88
|
+
- lib/html-griddler/configuration.rb
|
89
|
+
- lib/html-griddler/email.rb
|
90
|
+
- lib/html-griddler/email_parser.rb
|
91
|
+
- lib/html-griddler/engine.rb
|
92
|
+
- lib/html-griddler/errors.rb
|
93
|
+
- lib/html-griddler/version.rb
|
94
|
+
- lib/html-griddler.rb
|
95
|
+
- lib/tasks/griddler_tasks.rake
|
96
|
+
- LICENSE
|
97
|
+
- Rakefile
|
98
|
+
- README.md
|
99
|
+
homepage: http://htmlemailboilerplate.com
|
100
|
+
licenses: []
|
101
|
+
|
102
|
+
post_install_message:
|
103
|
+
rdoc_options: []
|
104
|
+
|
105
|
+
require_paths:
|
106
|
+
- app
|
107
|
+
- lib
|
108
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: 1.9.2
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
none: false
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: "0"
|
120
|
+
requirements: []
|
121
|
+
|
122
|
+
rubyforge_project:
|
123
|
+
rubygems_version: 1.8.15
|
124
|
+
signing_key:
|
125
|
+
specification_version: 3
|
126
|
+
summary: Fork of Thoughtbot's SendGrid Parse API client Rails Engine for HTML Email Specific Applications
|
127
|
+
test_files: []
|
128
|
+
|