mailcannon 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
- ---
2
- SHA1:
3
- metadata.gz: c76844e3b36af8d15ea57c40d77894449392a62c
4
- data.tar.gz: f0283cff4d2c2d18a6c25552b42ae64206334b84
5
- SHA512:
6
- metadata.gz: 4872da07cf07f5d27a8be700dfee7c52f1bb6685f9ce02946ede451554b121368168270889f5a3b7385553474ab28720760b3177cf3cae58cc7e4e262977d902
7
- data.tar.gz: 53c32841dd0e9f407827b899b76784816489729ad52f0d66d58602e59468617a2ac3b78bf00381975f4fa4225cead3c3563252e3f8397a5900fe55a7ebe4450c
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4d633fe39d924cee3797b5e2f133dbaa8abe23f2
4
+ data.tar.gz: b61dd21b5ad3d561e40cd15bc96e328e2cc76403
5
+ SHA512:
6
+ metadata.gz: 8c25da71c5a0387fd8d62b65cffb4d881b2d93917d80240e28fb7a23626fc17c355ca3dd634d7247c4ff5136c056b2a76f6dbd52329818662c16595fc8e37ede
7
+ data.tar.gz: cd55772c0f1a9eb64a67a1bea3501f56300409208dbc135cc18eb8aabe83715fc4c22dd592355f9b35f46911de4548a3381dff0abe984c14c5a758800e5bab33
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .DS_Store
2
+ pkg
3
+ tmp
4
+ env.sh
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color --format documentation
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ rbx-2.2.1
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ rvm:
2
+ - rbx-2.1.1
3
+ - 2.0.0-p353
4
+ - 1.9.3-p448
5
+ - jruby-19mode
6
+ before_install:
7
+ - gem install bundler
8
+ services:
9
+ - mongodb
10
+ - redis
data/Gemfile CHANGED
@@ -1,2 +1,22 @@
1
- source "https://rubygems.org"
2
- gemspec
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'redis'
4
+ gem 'mongoid'
5
+ gem 'sidekiq'
6
+ gem 'sendgrid_webapi'
7
+ gem 'librato-metrics'
8
+ gem 'rubysl', platform: :rbx
9
+ gem 'jruby-openssl', platform: :jruby
10
+
11
+ group :development do
12
+ gem 'rspec'
13
+ gem 'rspec-mocks'
14
+ gem 'rspec-expectations'
15
+ gem 'database_cleaner'
16
+ gem 'factory_girl'
17
+ gem 'vcr'
18
+ gem 'webmock', '>= 1.8.0', '< 1.16'
19
+ gem 'rake'
20
+ gem 'pry'
21
+ gem 'pry-nav'
22
+ end
data/Gemfile.lock CHANGED
@@ -1,14 +1,3 @@
1
- PATH
2
- remote: .
3
- specs:
4
- mailcannon (0.0.1)
5
- librato-metrics
6
- mongoid
7
- redis
8
- rubysl
9
- sendgrid_webapi
10
- sidekiq
11
-
12
1
  GEM
13
2
  remote: https://rubygems.org/
14
3
  specs:
@@ -18,13 +7,19 @@ GEM
18
7
  activesupport (3.2.16)
19
8
  i18n (~> 0.6, >= 0.6.4)
20
9
  multi_json (~> 1.0)
10
+ addressable (2.3.5)
21
11
  aggregate (0.2.2)
22
12
  builder (3.0.4)
23
13
  celluloid (0.15.2)
24
14
  timers (~> 1.1.0)
25
15
  coderay (1.0.9)
26
16
  connection_pool (1.2.0)
17
+ crack (0.4.1)
18
+ safe_yaml (~> 0.9.0)
19
+ database_cleaner (1.2.0)
27
20
  diff-lcs (1.2.4)
21
+ factory_girl (4.3.0)
22
+ activesupport (>= 3.0.0)
28
23
  faraday (0.8.8)
29
24
  multipart-post (~> 1.2.0)
30
25
  ffi2-generators (0.1.1)
@@ -264,6 +259,7 @@ GEM
264
259
  rubysl-xmlrpc (2.0.0)
265
260
  rubysl-yaml (2.0.4)
266
261
  rubysl-zlib (2.0.1)
262
+ safe_yaml (0.9.7)
267
263
  sendgrid_webapi (0.0.2)
268
264
  faraday (~> 0.8.0)
269
265
  json (~> 1.8.0)
@@ -276,14 +272,29 @@ GEM
276
272
  slop (3.4.5)
277
273
  timers (1.1.0)
278
274
  tzinfo (0.3.38)
275
+ vcr (2.8.0)
276
+ webmock (1.15.2)
277
+ addressable (>= 2.2.7)
278
+ crack (>= 0.3.2)
279
279
 
280
280
  PLATFORMS
281
281
  ruby
282
282
 
283
283
  DEPENDENCIES
284
- mailcannon!
284
+ database_cleaner
285
+ factory_girl
286
+ jruby-openssl
287
+ librato-metrics
288
+ mongoid
285
289
  pry
286
290
  pry-nav
287
291
  rake
292
+ redis
288
293
  rspec
294
+ rspec-expectations
289
295
  rspec-mocks
296
+ rubysl
297
+ sendgrid_webapi
298
+ sidekiq
299
+ vcr
300
+ webmock (>= 1.8.0, < 1.16)
data/Readme.md ADDED
@@ -0,0 +1,52 @@
1
+ [![Gem version](https://badge.fury.io/rb/mailcannon.png)](http://rubygems.org/gems/mailcannon) [![Code Climate](https://codeclimate.com/github/lucasmartins/mailcannon.png)](https://codeclimate.com/github/lucasmartins/mailcannon) [![Build Status](https://travis-ci.org/lucasmartins/mailcannon.png?branch=master)](https://travis-ci.org/lucasmartins/mailcannon) [![Dependency Status](https://gemnasium.com/lucasmartins/mailcannon.png)](https://gemnasium.com/lucasmartins/mailcannon)
2
+
3
+ MailCannon
4
+ ==========
5
+
6
+ This is a **WORK IN PROGRESS**
7
+
8
+ This Gem relies heavily on both [Sidekiq](https://github.com/mperham/sidekiq) and Celluloid Gems, you are encouraged to use it anywhere with Ruby (a http interface is on the Roadmap ).
9
+
10
+ This Gem provides workers ready for deploy cooked with [MongoDB](http://www.mongodb.org/) + [Mongoid](https://github.com/mongoid/mongoid) + [Sidekiq](https://github.com/mperham/sidekiq) + [Rubinius](http://rubini.us/) (feel free to use on MRI and jruby as well).
11
+
12
+ Install
13
+ =======
14
+
15
+ You can:
16
+ ```
17
+ $ gem install mailcannon
18
+ ```
19
+
20
+ Or just add it to your Gemfile
21
+ ```
22
+ gem 'mailcannon'
23
+ ```
24
+
25
+ Use
26
+ ===
27
+
28
+ ### Configuration file
29
+ If you are on Rails, run the following command to generate a config file:
30
+
31
+ `$ rails g mailcannon:config`
32
+
33
+ Edit the file to meet your environemnt needs.
34
+
35
+ Check the [specs](https://github.com/lucasmartins/mailcannon/tree/master/spec) to see the testing example, it will surely make it clearer.
36
+
37
+ Contribute
38
+ ==========
39
+
40
+ Just fork [MailCannon](https://github.com/lucasmartins/mailcannon), add your feature+spec, and make a pull request. Do not mess up with the version file though.
41
+
42
+ **NOTICE**: The project is at embrionary stage, breaking changes will apply.
43
+
44
+ Support
45
+ =======
46
+
47
+ This is an opensource project so don't expect premium support, but don't be shy, post any troubles you're having in the [Issues](https://github.com/lucasmartins/mailcannon/issues) page and we'll do what we can to help.
48
+
49
+ License
50
+ =======
51
+
52
+ MailCannon is free software under the [MIT license](http://lucasmartins.mit-license.org).
@@ -0,0 +1 @@
1
+ en:
@@ -0,0 +1 @@
1
+ pt-BR:
data/env.sample.sh ADDED
@@ -0,0 +1,7 @@
1
+ export REDIS_URL='redis://localhost:6379'
2
+ export SENDGRID_PASSWORD=''
3
+ export SENDGRID_USERNAME=''
4
+ export LIBRATO_SOURCE='staging'
5
+ export LIBRATO_USER=''
6
+ export LIBRATO_TOKEN=''
7
+
@@ -0,0 +1,15 @@
1
+ module MailCannon::Adapter
2
+
3
+ module InstanceMethods
4
+ def send!
5
+ raise 'Not available for this adapter!'
6
+ end
7
+ def send_bulk!
8
+ raise 'Not available for this adapter!'
9
+ end
10
+ end
11
+
12
+ def self.included(receiver)
13
+ receiver.send :include, InstanceMethods
14
+ end
15
+ end
@@ -0,0 +1,44 @@
1
+ module MailCannon::Adapter
2
+ module Sendgrid
3
+ include MailCannon::Adapter
4
+ module InstanceMethods
5
+ def send!
6
+ raise 'Invalid Document!' unless self.valid?
7
+ response = send_single_email
8
+ self.after_sent(successfully_sent?(response))
9
+ return response
10
+ end
11
+ end
12
+
13
+ def self.included(receiver)
14
+ receiver.send :include, InstanceMethods
15
+ end
16
+
17
+ private
18
+ def api_client
19
+ client = SendGridWebApi::Client.new(ENV['SENDGRID_USERNAME'], ENV['SENDGRID_PASSWORD'])
20
+ end
21
+
22
+ def send_single_email
23
+ api_client.mail.send(
24
+ :to => self.to,
25
+ :toname => self.to_name,
26
+ :subject => self.subject,
27
+ :text => self.mail.text,
28
+ :html => self.mail.html,
29
+ :from => self.from,
30
+ :fromname => self.from_name,
31
+ :bcc => self.bcc,
32
+ :replyto => self.reply_to
33
+ )
34
+ end
35
+
36
+ def successfully_sent?(response)
37
+ if response['message']=='success'
38
+ true
39
+ else
40
+ false
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,49 @@
1
+ load 'lib/mailcannon/adapters/sendgrid.rb'
2
+
3
+ class MailCannon::Envelope
4
+ include Mongoid::Document
5
+ include Mongoid::Timestamps
6
+ include MailCannon::Adapter::Sendgrid
7
+
8
+ has_one :mail
9
+ has_many :stamps
10
+
11
+ field :from, type: String
12
+ field :from_name, type: String
13
+ field :to, type: String
14
+ field :to_name, type: String
15
+ field :subject, type: String
16
+ field :bcc, type: String
17
+ field :reply_to, type: String
18
+ field :date, type: Date
19
+ field :xsmtpapi, type: Hash
20
+
21
+ validates :from, :to, :subject, :mail, presence: true
22
+ validates_associated :mail
23
+
24
+ after_create do |envelope|
25
+ envelope.stamp! MailCannon::Event::New.stamp
26
+ MailCannon::SingleBarrel.perform_async(envelope.id)
27
+ end
28
+
29
+ def stamp!(code)
30
+ self.class.valid_code_kind?(code)
31
+ self.stamps << MailCannon::Stamp.from_code(code)
32
+ end
33
+
34
+ def after_sent(response)
35
+ if response
36
+ stamp!(MailCannon::Event::Processed.stamp)
37
+ self.mail.destroy
38
+ self.mail=nil # to avoid reload
39
+ end
40
+ end
41
+
42
+ private
43
+ def self.valid_code_kind?(code)
44
+ unless [Fixnum, MailCannon::Stamp].include?(code.class) || MailCannon::Event.constants.include?(code.to_s.camelize.to_sym)
45
+ raise 'code must be an Integer, MailCannon::Event::*, or MailCannon::Stamp !'
46
+ end
47
+ end
48
+
49
+ end
@@ -0,0 +1,54 @@
1
+ class MailCannon::Event
2
+
3
+ EVENTS = [
4
+ 'new',
5
+ 'processed',
6
+ 'delivered',
7
+ 'open',
8
+ 'click',
9
+ 'deferred',
10
+ 'spam_report',
11
+ 'spam',
12
+ 'unsubscribe',
13
+ 'drop',
14
+ 'bounce'
15
+ ]
16
+
17
+ EVENTS.each do |module_name|
18
+ MailCannon::Event.class_eval <<RUBY
19
+ module #{module_name.camelize}
20
+ def self.to_i
21
+ #{EVENTS.index(module_name)}
22
+ end
23
+ def self.to_s
24
+ "#{module_name}"
25
+ end
26
+ def self.stamp
27
+ MailCannon::Stamp.new({code: #{EVENTS.index(module_name)} })
28
+ end
29
+ def self.to_stamp
30
+ self.stamp
31
+ end
32
+ end
33
+ RUBY
34
+ end # ends each loop
35
+
36
+ def self.from_code(code)
37
+ raise 'code must be an Integer or String!' unless code.is_a?(Integer)||code.is_a?(String)
38
+ if code.is_a?(Integer)
39
+ return eval_module(EVENTS[code])
40
+ else
41
+ return eval_module(code)
42
+ end
43
+ end
44
+
45
+ private
46
+ def self.eval_module(code)
47
+ if EVENTS.include?(code)
48
+ return eval("MailCannon::Event::#{code.camelize}")
49
+ else
50
+ raise "invalid code. Use one of the following: #{EVENTS}"
51
+ end
52
+ end
53
+
54
+ end
@@ -0,0 +1,11 @@
1
+ class MailCannon::Mail
2
+ include Mongoid::Document
3
+ include Mongoid::Timestamps
4
+
5
+ belongs_to :envelope, index: true
6
+
7
+ field :text, type: String
8
+ field :html, type: String
9
+
10
+ validate :text, presence: true
11
+ end
@@ -0,0 +1,24 @@
1
+ class MailCannon::Stamp
2
+ include Mongoid::Document
3
+ include Mongoid::Timestamps
4
+
5
+ belongs_to :envelope, index: true
6
+
7
+ field :code, type: Integer, default: 0
8
+
9
+ validate :code, :envelope, presence: true
10
+
11
+ def event
12
+ MailCannon::Event.from_code(self.code)
13
+ end
14
+
15
+ def self.from_code(code)
16
+ if code.is_a? Fixnum
17
+ return MailCannon::Stamp.new({code: code})
18
+ elsif code.is_a? MailCannon::Stamp
19
+ return code
20
+ else # MailCannon::Event::*
21
+ return code.stamp
22
+ end
23
+ end
24
+ end
@@ -2,7 +2,7 @@ module MailCannon
2
2
  module Version
3
3
  MAJOR = 0
4
4
  MINOR = 0
5
- PATCH = 1
5
+ PATCH = 2
6
6
  STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
7
7
  end
8
8
  end
@@ -0,0 +1,11 @@
1
+ class MailCannon::MultiBarrel
2
+ include Sidekiq::Worker
3
+
4
+ def perform
5
+ raise 'Worker unavailable!'
6
+ # search pending emails in the db
7
+ # build the xsmtpapi Hash
8
+ # bulk_send!
9
+ # callbacks
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ class MailCannon::SingleBarrel
2
+ include Sidekiq::Worker
3
+
4
+ def perform(envelope_id)
5
+ aggregator = Librato::Metrics::Aggregator.new
6
+ aggregator.time 'mailcannon.shooter.perform' do
7
+ envelope_id = envelope_id['$oid'] if envelope_id['$oid']
8
+ puts "sending MailCannon::Envelope.find('#{envelope_id}')"
9
+
10
+ begin
11
+ envelope = MailCannon::Envelope.find(envelope_id)
12
+ if envelope.valid?
13
+ response = envelope.send!
14
+ unless response==true
15
+ raise response
16
+ end
17
+ end
18
+ rescue Exception => e
19
+ puts "unable to send MailCannon::Envelope.find(#{envelope_id})"
20
+ puts e.backtrace
21
+ end
22
+ end
23
+ aggregator.submit
24
+ end
25
+ end
data/lib/mailcannon.rb CHANGED
@@ -1,3 +1,16 @@
1
+ require 'yaml'
2
+ require 'openssl'
3
+ require 'bundler'
4
+ require 'json'
5
+ require 'mongoid'
6
+
7
+ case ENV['RACK_ENV']
8
+ when 'production'
9
+ Bundler.require(:default)
10
+ else
11
+ Bundler.require(:default,:development)
12
+ end
13
+
1
14
  Encoding.default_internal = "utf-8"
2
15
  Encoding.default_external = "utf-8"
3
16
 
@@ -9,8 +22,25 @@ class Module
9
22
  end
10
23
 
11
24
  module MailCannon
12
-
13
- #load 'mailcannon/class.rb'
25
+
26
+ load 'mailcannon/envelope.rb'
27
+ load 'mailcannon/mail.rb'
28
+ load 'mailcannon/stamp.rb'
29
+ load 'mailcannon/event.rb'
30
+ load 'mailcannon/adapter.rb'
31
+ load 'mailcannon/adapters/sendgrid.rb'
32
+ load 'mailcannon/workers/single_barrel.rb'
33
+ load 'mailcannon/version.rb'
34
+
35
+ self.warmode if ENV['MAILCANNON_MODE']=='war'
36
+
37
+ def self.warmode
38
+ Bundler.require(:default)
39
+ Mongoid.load!("config/mongoid.yml", ENV['RACK_ENV']) # change to env URL
40
+ Librato::Metrics.authenticate(ENV['LIBRATO_USER'], ENV['LIBRATO_TOKEN']) if ENV['LIBRATO_TOKEN'] && ENV['LIBRATO_USER'] # change to initializer
41
+ redis_uri = URI.parse(ENV['REDIS_URL'])
42
+ $redis = Redis.new(host: redis_uri.host, port: redis_uri.port)
43
+ end
14
44
 
15
45
  def self.config(root_dir=nil)
16
46
  @config ||= load_config(root_dir)
@@ -24,7 +54,7 @@ module MailCannon
24
54
  raise "Couldn't find config yml at #{path}." unless File.file?(path)
25
55
  content = File.read(path)
26
56
  erb = ERB.new(content).result
27
- YAML.load(erb).with_indifferent_access
57
+ YAML.load(erb)
28
58
  end
29
59
 
30
60
  end
data/mailcannon.gemspec CHANGED
@@ -27,8 +27,13 @@ Gem::Specification.new do |s|
27
27
  s.add_dependency 'rubysl'
28
28
  #s.add_dependency 'jruby-openssl'
29
29
 
30
+ s.add_development_dependency "vcr"
30
31
  s.add_development_dependency "rspec"
31
32
  s.add_development_dependency "rspec-mocks"
33
+ s.add_development_dependency "rspec-expectations"
34
+ s.add_development_dependency "webmock", '>= 1.8.0', '< 1.16'
35
+ s.add_development_dependency "database_cleaner"
36
+ s.add_development_dependency "factory_girl"
32
37
  s.add_development_dependency "rake"
33
38
  s.add_development_dependency "pry"
34
39
  s.add_development_dependency "pry-nav"
@@ -0,0 +1,8 @@
1
+ FactoryGirl.define do
2
+ factory :envelope, class: MailCannon::Envelope do
3
+ from 'test@mailcannon.com'
4
+ to 'lucasmartins@railsnapraia.com'
5
+ subject 'Test'
6
+ mail factory: :mail
7
+ end
8
+ end
@@ -0,0 +1,6 @@
1
+ FactoryGirl.define do
2
+ factory :mail, class: MailCannon::Mail do
3
+ text "If you can't read the HTML content, you're screwed!"
4
+ html "<html><body><p>You should see what happens when your email client can't read HTML content.</p></body></html>"
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ FactoryGirl.define do
2
+ factory :stamp, class: MailCannon::Stamp do
3
+ code 0
4
+ envelope factory: :envelope
5
+ end
6
+ end
@@ -0,0 +1,36 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://sendgrid.com/api/mail.send.json
6
+ body:
7
+ encoding: US-ASCII
8
+ string: to=lucasmartins%40railsnapraia.com&toname&subject=Test&text=If+you+can%27t+read+the+HTML+content%2C+you%27re+screwed%21&html=%3Chtml%3E%3Cbody%3E%3Cp%3EYou+should+see+what+happens+when+your+email+client+can%27t+read+HTML+content.%3C%2Fp%3E%3C%2Fbody%3E%3C%2Fhtml%3E&from=test%40mailcannon.com&fromname&bcc&replyto&api_user=app5967346%40heroku.com&api_key=i1att6wi
9
+ headers:
10
+ User-Agent:
11
+ - Faraday v0.8.8
12
+ Accept-Encoding:
13
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
14
+ Accept:
15
+ - '*/*'
16
+ response:
17
+ status:
18
+ code: 200
19
+ message: OK
20
+ headers:
21
+ Server:
22
+ - nginx/1.4.2
23
+ Date:
24
+ - Sun, 15 Dec 2013 18:53:38 GMT
25
+ Content-Type:
26
+ - application/json
27
+ Transfer-Encoding:
28
+ - chunked
29
+ Connection:
30
+ - keep-alive
31
+ body:
32
+ encoding: UTF-8
33
+ string: '{"message":"success"}'
34
+ http_version:
35
+ recorded_at: Sun, 15 Dec 2013 18:53:33 GMT
36
+ recorded_with: VCR 2.8.0
@@ -0,0 +1,36 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://sendgrid.com/api/mail.send.json
6
+ body:
7
+ encoding: US-ASCII
8
+ string: to=lucasmartins%40railsnapraia.com&toname&subject=Test&text=If+you+can%27t+read+the+HTML+content%2C+you%27re+screwed%21&html=%3Chtml%3E%3Cbody%3E%3Cp%3EYou+should+see+what+happens+when+your+email+client+can%27t+read+HTML+content.%3C%2Fp%3E%3C%2Fbody%3E%3C%2Fhtml%3E&from=test%40mailcannon.com&fromname&bcc&replyto&api_user=app5967346%40heroku.com&api_key=i1att6wi
9
+ headers:
10
+ User-Agent:
11
+ - Faraday v0.8.8
12
+ Accept-Encoding:
13
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
14
+ Accept:
15
+ - '*/*'
16
+ response:
17
+ status:
18
+ code: 200
19
+ message: OK
20
+ headers:
21
+ Server:
22
+ - nginx/1.4.2
23
+ Date:
24
+ - Sun, 15 Dec 2013 18:59:55 GMT
25
+ Content-Type:
26
+ - application/json
27
+ Transfer-Encoding:
28
+ - chunked
29
+ Connection:
30
+ - keep-alive
31
+ body:
32
+ encoding: UTF-8
33
+ string: '{"message":"success"}'
34
+ http_version:
35
+ recorded_at: Sun, 15 Dec 2013 18:59:50 GMT
36
+ recorded_with: VCR 2.8.0
@@ -0,0 +1,19 @@
1
+ require "spec_helper"
2
+
3
+ describe MailCannon::Adapter::Sendgrid do
4
+ describe "#send!" do
5
+ let(:envelope) { build(:envelope) }
6
+ it "sends http request for Sendgrid web API" do
7
+ VCR.use_cassette('mailcannon_adapter_sendgrid_send') do
8
+ expect(envelope.send!).to eq({"message"=>"success"})
9
+ end
10
+ end
11
+ it "calls after_sent callback" do
12
+ VCR.use_cassette('mailcannon_adapter_sendgrid_send') do
13
+ envelope.should_receive(:after_sent)
14
+ envelope.send!
15
+ end
16
+ end
17
+ end
18
+ end
19
+