html-griddler 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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
+ ![thoughtbot](http://thoughtbot.com/images/tm/logo.png)
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,6 @@
1
+ class Griddler::EmailsController < ActionController::Base
2
+ def create
3
+ Griddler::Email.new(params).process
4
+ head :ok
5
+ end
6
+ end
@@ -0,0 +1 @@
1
+ require 'griddler'
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ Rails.application.routes.draw do
2
+ post '/email_processor' => 'griddler/emails#create', as: :email_processor
3
+ end
@@ -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
@@ -0,0 +1,4 @@
1
+ module Griddler
2
+ class Engine < ::Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,9 @@
1
+ module Griddler
2
+ class Error < StandardError
3
+ end
4
+
5
+ module Errors
6
+ class EmailBodyNotFound < Griddler::Error
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module HtmlGriddler
2
+ VERSION = "0.1.2"
3
+ end
@@ -0,0 +1,8 @@
1
+ require 'html-griddler/errors'
2
+ require 'html-griddler/engine'
3
+ require 'html-griddler/email'
4
+ require 'html-griddler/email_parser'
5
+ require 'html-griddler/configuration'
6
+
7
+ module Griddler
8
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :griddler do
3
+ # # Task goes here
4
+ # 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
+