mailcannon 0.0.1 → 0.0.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.
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
+