em-mailer 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rvmrc +2 -0
- data/Gemfile +4 -0
- data/README +120 -0
- data/Rakefile +10 -0
- data/bin/em_mailer_server +76 -0
- data/em-mailer.gemspec +30 -0
- data/lib/em-mailer.rb +25 -0
- data/lib/em-mailer/client.rb +33 -0
- data/lib/em-mailer/version.rb +5 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/unit/client_spec.rb +78 -0
- metadata +127 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
data/Gemfile
ADDED
data/README
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
#README
|
2
|
+
|
3
|
+
WARNING: This is an experiment.
|
4
|
+
|
5
|
+
You probably don't want to use this (quite yet). I haven't even begun
|
6
|
+
to see if it works on Rails 3. But, I can assure you it *is* working
|
7
|
+
in production for an old Rails 2 customer.
|
8
|
+
|
9
|
+
For those that have read the aforementioned caveats and are gluttons
|
10
|
+
for punishment, please read on.
|
11
|
+
|
12
|
+
##Why?
|
13
|
+
I wrote em-mailer to solve a problem that I was having. I wanted to
|
14
|
+
send e-mails (to a fair # of subscribers) asynchronously. I didn't
|
15
|
+
want to use Resque or another background job / worker solution for
|
16
|
+
this, I wanted a drop-dead simple mail server process whose sole job
|
17
|
+
was to accept Mail messages encoded as JSON over a socket, and then
|
18
|
+
deliver them according to the delivery options (I've tested both
|
19
|
+
:sendmail and :smtp configurations).
|
20
|
+
|
21
|
+
##Rails 2
|
22
|
+
I originally wrote em_mailer for an old Rails 2 app that I hadn't
|
23
|
+
bothered getting around to migrating, so there's built-in support
|
24
|
+
for similarly lazy-minded individuals.
|
25
|
+
|
26
|
+
In config/environment.rb:
|
27
|
+
|
28
|
+
config.gem 'em_mailer'
|
29
|
+
|
30
|
+
In config/environments/production.rb
|
31
|
+
|
32
|
+
config.action_mailer.delivery_method = :em_mailer
|
33
|
+
|
34
|
+
Out of the box it works with :sendmail delivery as the default.
|
35
|
+
|
36
|
+
If you wish to use SMTP you can do something like this:
|
37
|
+
|
38
|
+
In config/initializers/em_mailer.rb
|
39
|
+
|
40
|
+
ActionMailer::Base.em_mailer_settings[:delivery_method] = :smtp
|
41
|
+
ActionMailer::Base.em_mailer_settings[:delivery_options] = {
|
42
|
+
:address => '<< your smtp server name>>',
|
43
|
+
:port => 25,
|
44
|
+
:domain => '<< your domain >>',
|
45
|
+
:authentication => :plain,
|
46
|
+
:user_name => '<< user with access to smtp host >>',
|
47
|
+
:password => '<< their password >>'
|
48
|
+
}
|
49
|
+
|
50
|
+
##Rails 3
|
51
|
+
In your Gemfile
|
52
|
+
|
53
|
+
gem 'em_mailer'
|
54
|
+
|
55
|
+
That should be all that's necessary. Again, haven't experimented (yet).
|
56
|
+
|
57
|
+
##Running the Mail Server
|
58
|
+
You'll need a running instance of the mail server running. The gem
|
59
|
+
comes bundled with a binary em_mailer_server. You can run it from
|
60
|
+
a command line thusly:
|
61
|
+
|
62
|
+
em_mailer_server
|
63
|
+
|
64
|
+
To run it daemonized it's as easy as:
|
65
|
+
|
66
|
+
em_mailer_server -D
|
67
|
+
|
68
|
+
You can also specify the port that you want the server to listen on
|
69
|
+
(default is 5600)
|
70
|
+
|
71
|
+
em_mailer_server -p 5600 -D
|
72
|
+
|
73
|
+
For a full list of configuration options pass it a -h, or --help.
|
74
|
+
|
75
|
+
##Start/Stop Script
|
76
|
+
I happen to be running em_mailer_server on a CentOS box. Here's my
|
77
|
+
/etc/init.d/em_mailer script that I use to start|stop|restart the
|
78
|
+
process on the host server
|
79
|
+
|
80
|
+
#!/bin/bash
|
81
|
+
#
|
82
|
+
# EM Mailer
|
83
|
+
#
|
84
|
+
# chkconfig: - 85 15
|
85
|
+
# description: start, stop, restart EM Mailer Server
|
86
|
+
#
|
87
|
+
RETVAL=0
|
88
|
+
|
89
|
+
case "$1" in
|
90
|
+
start)
|
91
|
+
/usr/local/bin/em_mailer_server -P /var/run/em_mailer.pid -D
|
92
|
+
RETVAL=$?
|
93
|
+
;;
|
94
|
+
stop)
|
95
|
+
kill `cat /var/run/em_mailer.pid`
|
96
|
+
RETVAL=$?
|
97
|
+
;;
|
98
|
+
restart)
|
99
|
+
kill `cat /var/run/em_mailer.pid`
|
100
|
+
/usr/local/bin/em_mailer_server -P /var/run/em_mailer.pid -D
|
101
|
+
RETVAL=$?
|
102
|
+
;;
|
103
|
+
*)
|
104
|
+
echo "Usage: em_mailer {start|stop|restart}"
|
105
|
+
exit 1
|
106
|
+
;;
|
107
|
+
esac
|
108
|
+
|
109
|
+
exit $RETVAL
|
110
|
+
|
111
|
+
##Monitoring
|
112
|
+
Lastly, I like to use monit to make sure my daemon processes are up and
|
113
|
+
running. Here's my sample monit script:
|
114
|
+
|
115
|
+
check process em_mailer with pidfile /var/run/em_mailer.pid
|
116
|
+
start program = "/etc/init.d/em_mailer start"
|
117
|
+
stop program = "/etc/init.d/em_mailer stop"
|
118
|
+
if failed port 5600 then restart
|
119
|
+
group mail
|
120
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'eventmachine'
|
5
|
+
require 'optparse'
|
6
|
+
require 'mail'
|
7
|
+
require 'yajl'
|
8
|
+
|
9
|
+
options = {}
|
10
|
+
|
11
|
+
optparse = OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: em_mailer_server [options]"
|
13
|
+
|
14
|
+
options[:host] = '127.0.0.1'
|
15
|
+
opts.on('-o', '--host HOST', 'listen on HOST (default: 127.0.0.1)') do |host|
|
16
|
+
options[:host] = host
|
17
|
+
end
|
18
|
+
|
19
|
+
options[:port] = 5600
|
20
|
+
opts.on('-p', '--port PORT', 'use PORT (default: 5600)') do |port|
|
21
|
+
options[:port] = port
|
22
|
+
end
|
23
|
+
|
24
|
+
options[:pid] = 'tmp/pids/em_mailer.pid'
|
25
|
+
opts.on('-P', '--pid PID', 'specify path to write PID (default: tmp/pids/em_mailer.pid)') do |pid|
|
26
|
+
options[:pid] = pid
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on('-D', '--daemonize', 'run daemonized in the background') { |d|
|
30
|
+
options[:daemonize] = d ? true : false
|
31
|
+
}
|
32
|
+
|
33
|
+
opts.on('-h', '--help', 'display this screen') do
|
34
|
+
puts opts
|
35
|
+
exit 0
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.parse! ARGV
|
39
|
+
end
|
40
|
+
|
41
|
+
module MailServer
|
42
|
+
def post_init
|
43
|
+
@parser = Yajl::Parser.new(:symbolize_keys => true)
|
44
|
+
@parser.on_parse_complete = Proc.new { |obj| object_parsed(obj) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def object_parsed(obj)
|
48
|
+
puts "delivering #{obj[:message]}"
|
49
|
+
mail = Mail.new(obj[:message])
|
50
|
+
mail.delivery_method obj[:delivery_method].to_sym, obj[:delivery_options]
|
51
|
+
mail.deliver
|
52
|
+
end
|
53
|
+
|
54
|
+
def receive_data(data)
|
55
|
+
@parser << data
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
EM.run{
|
60
|
+
EM.start_server options[:host], options[:port], MailServer
|
61
|
+
if options[:daemonize]
|
62
|
+
if RUBY_VERSION < "1.9"
|
63
|
+
exit if fork
|
64
|
+
Process.setsid
|
65
|
+
exit if fork
|
66
|
+
Dir.chdir "/"
|
67
|
+
File.umask 0000
|
68
|
+
STDIN.reopen "/dev/null"
|
69
|
+
STDOUT.reopen "/dev/null", "a"
|
70
|
+
STDERR.reopen "/dev/null", "a"
|
71
|
+
else
|
72
|
+
Process.daemon
|
73
|
+
end
|
74
|
+
end
|
75
|
+
File.open(options[:pid], 'w') { |f| f << Process.pid }
|
76
|
+
}
|
data/em-mailer.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "em-mailer/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "em-mailer"
|
7
|
+
s.version = Em::Mailer::VERSION
|
8
|
+
s.authors = ["Chris Kraybill"]
|
9
|
+
s.email = ["ckraybill@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Asynchronous mail client and server}
|
12
|
+
s.description = %q{Asynchronous mail client and server written in ruby,
|
13
|
+
eventmachine to replace synchronous mail implementation
|
14
|
+
in Rails 2 and 3}
|
15
|
+
|
16
|
+
s.rubyforge_project = "em-mailer"
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
|
23
|
+
s.add_dependency "eventmachine"
|
24
|
+
s.add_dependency "mail"
|
25
|
+
s.add_dependency "yajl-ruby"
|
26
|
+
s.add_dependency "actionmailer"
|
27
|
+
|
28
|
+
s.add_development_dependency "rspec"
|
29
|
+
s.add_development_dependency "em-spec"
|
30
|
+
end
|
data/lib/em-mailer.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "action_mailer"
|
2
|
+
require "em-mailer/version"
|
3
|
+
require "em-mailer/client"
|
4
|
+
|
5
|
+
if defined?(Rails) && Rails::VERSION::MAJOR == 2
|
6
|
+
module ActionMailer
|
7
|
+
class Base
|
8
|
+
@@em_mailer_settings = {
|
9
|
+
:host => 'localhost',
|
10
|
+
:port => 5600,
|
11
|
+
:delivery_method => :sendmail,
|
12
|
+
:delivery_options => {}
|
13
|
+
}
|
14
|
+
cattr_accessor :em_mailer_settings
|
15
|
+
|
16
|
+
def perform_delivery_em_mailer(mail)
|
17
|
+
em_client = Em::Mailer::Client.new(em_mailer_settings)
|
18
|
+
em_client.deliver!(mail)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
else
|
23
|
+
ActionMailer::Base.add_delivery_method :em_mailer, Em::Mailer::Client
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'yajl'
|
3
|
+
|
4
|
+
module Em
|
5
|
+
module Mailer
|
6
|
+
class Client
|
7
|
+
def initialize(values)
|
8
|
+
self.settings = {
|
9
|
+
:host => 'localhost',
|
10
|
+
:port => 5600,
|
11
|
+
:delivery_method => :sendmail,
|
12
|
+
:delivery_options => {}
|
13
|
+
}.merge!(values)
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_accessor :settings
|
17
|
+
|
18
|
+
def deliver!(mail)
|
19
|
+
socket = open_socket
|
20
|
+
Yajl::Encoder.encode({
|
21
|
+
:message => mail.to_s,
|
22
|
+
:delivery_method => settings[:delivery_method],
|
23
|
+
:delivery_options => settings[:delivery_options]
|
24
|
+
}, socket)
|
25
|
+
socket.close
|
26
|
+
end
|
27
|
+
|
28
|
+
def open_socket
|
29
|
+
TCPSocket.new(settings[:host], settings[:port])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Em::Mailer::Client do
|
4
|
+
describe "#initialize" do
|
5
|
+
before do
|
6
|
+
@client = Em::Mailer::Client.new({})
|
7
|
+
end
|
8
|
+
|
9
|
+
it "default's host to localhost" do
|
10
|
+
@client.settings[:host].should == 'localhost'
|
11
|
+
end
|
12
|
+
|
13
|
+
it "default's port to 5600" do
|
14
|
+
@client.settings[:port].should == 5600
|
15
|
+
end
|
16
|
+
|
17
|
+
it "default's delivery method to :sendmail" do
|
18
|
+
@client.settings[:delivery_method].should == :sendmail
|
19
|
+
end
|
20
|
+
|
21
|
+
it "default's delivery options to an empty hash" do
|
22
|
+
@client.settings[:delivery_options].should == {}
|
23
|
+
end
|
24
|
+
|
25
|
+
it "allows overridding the port" do
|
26
|
+
@client = Em::Mailer::Client.new({:port => 5700})
|
27
|
+
@client.settings[:port].should == 5700
|
28
|
+
end
|
29
|
+
|
30
|
+
it "allows overridding the delivery method" do
|
31
|
+
@client = Em::Mailer::Client.new({:delivery_method => :smtp})
|
32
|
+
@client.settings[:delivery_method].should == :smtp
|
33
|
+
end
|
34
|
+
|
35
|
+
it "allows overridding the delivery options" do
|
36
|
+
@client = Em::Mailer::Client.new({:delivery_options => {:username => 'meh'}})
|
37
|
+
@client.settings[:delivery_options][:username].should == 'meh'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "#deliver!" do
|
42
|
+
before do
|
43
|
+
@mail = { :to => 'bleh@foo.com', :subject => 'Hai!' }
|
44
|
+
@socket = mock(TCPSocket, :close => true)
|
45
|
+
@client = Em::Mailer::Client.new({:host => 'foo', :port => 42})
|
46
|
+
@client.stub(:open_socket).and_return(@socket)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "opens a socket" do
|
50
|
+
@client.should_receive(:open_socket)
|
51
|
+
@client.deliver!(@mail)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "encodes a hash, along with the socket" do
|
55
|
+
Yajl::Encoder.should_receive(:encode).with(an_instance_of(Hash),@socket)
|
56
|
+
@client.deliver!(@mail)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "converts the mail message to a String" do
|
60
|
+
@mail.should_receive(:to_s)
|
61
|
+
@client.deliver!(@mail)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "closes the socket" do
|
65
|
+
@socket.should_receive(:close)
|
66
|
+
@client.deliver!(@mail)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#open_socket" do
|
71
|
+
it "instantiates a socket on the correct host and port" do
|
72
|
+
@client = Em::Mailer::Client.new({:host => 'foo', :port => 42})
|
73
|
+
TCPSocket.should_receive(:new).with('foo',42)
|
74
|
+
@client.open_socket
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
metadata
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: em-mailer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Chris Kraybill
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-01-01 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: eventmachine
|
16
|
+
requirement: &70103820724380 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70103820724380
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: mail
|
27
|
+
requirement: &70103820723920 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70103820723920
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: yajl-ruby
|
38
|
+
requirement: &70103820723500 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70103820723500
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: actionmailer
|
49
|
+
requirement: &70103820723080 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70103820723080
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rspec
|
60
|
+
requirement: &70103820722660 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70103820722660
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: em-spec
|
71
|
+
requirement: &70103820722240 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70103820722240
|
80
|
+
description: ! "Asynchronous mail client and server written in ruby,\n eventmachine
|
81
|
+
to replace synchronous mail implementation\n in Rails 2 and 3"
|
82
|
+
email:
|
83
|
+
- ckraybill@gmail.com
|
84
|
+
executables:
|
85
|
+
- em_mailer_server
|
86
|
+
extensions: []
|
87
|
+
extra_rdoc_files: []
|
88
|
+
files:
|
89
|
+
- .gitignore
|
90
|
+
- .rvmrc
|
91
|
+
- Gemfile
|
92
|
+
- README
|
93
|
+
- Rakefile
|
94
|
+
- bin/em_mailer_server
|
95
|
+
- em-mailer.gemspec
|
96
|
+
- lib/em-mailer.rb
|
97
|
+
- lib/em-mailer/client.rb
|
98
|
+
- lib/em-mailer/version.rb
|
99
|
+
- spec/spec_helper.rb
|
100
|
+
- spec/unit/client_spec.rb
|
101
|
+
homepage: ''
|
102
|
+
licenses: []
|
103
|
+
post_install_message:
|
104
|
+
rdoc_options: []
|
105
|
+
require_paths:
|
106
|
+
- lib
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ! '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ! '>='
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
requirements: []
|
120
|
+
rubyforge_project: em-mailer
|
121
|
+
rubygems_version: 1.8.10
|
122
|
+
signing_key:
|
123
|
+
specification_version: 3
|
124
|
+
summary: Asynchronous mail client and server
|
125
|
+
test_files:
|
126
|
+
- spec/spec_helper.rb
|
127
|
+
- spec/unit/client_spec.rb
|