kelredd-mailer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,112 @@
1
+ = Mailer
2
+
3
+ == Description
4
+
5
+ This is just a little gem to let you configure and send email using TMail over Net:SMTP.
6
+
7
+ == Installation
8
+
9
+ sudo gem install kelredd-mailer --source http://gemcutter.org
10
+
11
+ == Usage Examples
12
+
13
+ === A simple configuration
14
+
15
+ require 'mailer'
16
+
17
+ Mailer.configure do |config|
18
+ config.smtp_server = "smtp.gmail.com"
19
+ config.smtp_helo_domain = "example.com"
20
+ config.smtp_port = 587
21
+ config.smtp_username = "test@example.com"
22
+ config.smtp_password = 'secret'
23
+ config.environment = Mailer.production # not required, default is Mailer.development
24
+ end
25
+
26
+ Mailer.send(:to => "you@example.com", :subject => "a message for you") do
27
+ "here is a message for you"
28
+ end
29
+
30
+
31
+ === A more complex configuration
32
+
33
+ # Note: this is showing a more complex configuration
34
+ # only setup or configure what you need or want to
35
+
36
+ require 'mailer'
37
+
38
+ Mailer.configure do |config|
39
+ config.smtp_server = "smtp.gmail.com"
40
+ config.smtp_helo_domain = "example.com"
41
+ config.smtp_port = 587
42
+ config.smtp_username = "test@example.com"
43
+ config.smtp_password = 'secret'
44
+
45
+ config.environment = Mailer.production # not required, default is Mailer.development
46
+ config.smtp_auth_type = "plain" # not required, default is "login"
47
+ config.default_from = "me@example.com" # not required, default is config.smtp_username
48
+ # if it is a valid email address
49
+
50
+ config.log_file = "log/email.log" # not required: setup a file to log mails to
51
+ end
52
+
53
+ # send mails with the configured mailer...
54
+ # note: only requires :from (if no config.default_from), :to, and :subject
55
+ Mailer.send({
56
+ :from => 'bob@example.com', # not required, defaults to config.default_from
57
+ :reply_to => 'bob@me.com', # not required
58
+ :to => "you@example.com",
59
+ :cc => "Al <another@example.com>", # send with specific naming
60
+ :bcc => ["one@example.com", "two@example.com"], # send to multiple addresses
61
+ :subject => "a message"
62
+ }) do
63
+ # not required, don't pass .send a block if you don't want your mail to have a body
64
+ "a message body"
65
+ end
66
+
67
+ == Testing
68
+ Mailer has some helpers and Shoulda macros to ease testing that emails were delivered with the correct parameters, fields, and content.
69
+
70
+ Note: This testing only tests that mail objects were built successfully and passed all checks for delivery. This does not actually send the mails or test sending the mails at the Net::SMTP level.
71
+
72
+ === Helpers
73
+ In test_helper.rb or wherever:
74
+
75
+ require 'mailer/test_helpers'
76
+ include Mailer::TestHelpers
77
+
78
+ === Shoulda Macros
79
+ In test_helper.rb or wherever:
80
+
81
+ require 'mailer/shoulda_macros/test_unit'
82
+
83
+ === Notes / TODOs
84
+
85
+ It's only live testing and known to be working with SMTP servers requiring TLS (ala Gmail). I want to extend it to support some auth configuration and switching so that it works with SMTP, SMTPS, and SMTP/TLS.
86
+
87
+ Right now, the Mailer can only have one configuration. Maybe like to extend it to create instances of Mailers with different configurations?
88
+
89
+ == License
90
+
91
+ Copyright (c) 2009 Kelly Redding
92
+
93
+ Permission is hereby granted, free of charge, to any person
94
+ obtaining a copy of this software and associated documentation
95
+ files (the "Software"), to deal in the Software without
96
+ restriction, including without limitation the rights to use,
97
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
98
+ copies of the Software, and to permit persons to whom the
99
+ Software is furnished to do so, subject to the following
100
+ conditions:
101
+
102
+ The above copyright notice and this permission notice shall be
103
+ included in all copies or substantial portions of the Software.
104
+
105
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
106
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
107
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
108
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
109
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
110
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
111
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
112
+ OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,67 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rake/testtask'
4
+
5
+ require 'lib/mailer/version'
6
+
7
+ spec = Gem::Specification.new do |s|
8
+ s.name = 'kelredd-mailer'
9
+ s.version = Mailer::Version.to_s
10
+ s.has_rdoc = true
11
+ s.extra_rdoc_files = %w(README.rdoc)
12
+ s.rdoc_options = %w(--main README.rdoc)
13
+ s.summary = "This gem is just a simple mailer interface."
14
+ s.author = 'Kelly Redding'
15
+ s.email = 'kelly@kelredd.com'
16
+ s.homepage = 'http://github.com/kelredd/mailer'
17
+ s.files = %w(README.rdoc Rakefile) + Dir.glob("{lib}/**/*")
18
+ # s.executables = ['mailer']
19
+
20
+ s.add_dependency('log4r')
21
+ s.add_dependency('tmail', '>= 1.2.3.0')
22
+
23
+ # to run the test suite, you will need this as well
24
+ #s.add_dependency('kelredd-useful', '>= 0.2.0') # gem install kelredd-useful --source http://gemcutter.org
25
+ end
26
+
27
+ Rake::GemPackageTask.new(spec) do |pkg|
28
+ pkg.gem_spec = spec
29
+ end
30
+
31
+ Rake::TestTask.new do |t|
32
+ t.libs << 'test'
33
+ t.test_files = FileList["test/**/*_test.rb"]
34
+ t.verbose = true
35
+ end
36
+
37
+ begin
38
+ require 'rcov/rcovtask'
39
+
40
+ Rcov::RcovTask.new(:coverage) do |t|
41
+ t.libs = ['test']
42
+ t.test_files = FileList["test/**/*_test.rb"]
43
+ t.verbose = true
44
+ t.rcov_opts = ['--text-report', "-x #{Gem.path}", '-x /Library/Ruby', '-x /usr/lib/ruby']
45
+ end
46
+
47
+ task :default => :coverage
48
+
49
+ rescue LoadError
50
+ warn "\n**** Install rcov (sudo gem install relevance-rcov) to get coverage stats ****\n"
51
+ task :default => :test
52
+ end
53
+
54
+
55
+ desc 'Generate the gemspec to serve this gem'
56
+ task :gemspec do
57
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
58
+ File.open(file, 'w') {|f| f << spec.to_ruby }
59
+ puts "Created gemspec: #{file}"
60
+ end
61
+
62
+ require 'cucumber'
63
+ require 'cucumber/rake/task'
64
+
65
+ Cucumber::Rake::Task.new(:features) do |t|
66
+ t.cucumber_opts = "test/features --format pretty"
67
+ end
@@ -0,0 +1,51 @@
1
+ require 'mailer/exceptions'
2
+
3
+ module Mailer
4
+ class Config
5
+
6
+ # TODO: look into abstracting port settings better based on server access type
7
+ # => ie, TLS or SSL or whatever
8
+ CONFIGS = [:smtp_helo_domain, :smtp_server, :smtp_port, :smtp_username, :smtp_password, :smtp_auth_type, :environment, :default_from]
9
+ NOT_REQUIRED = [:default_from]
10
+ CONFIGS.each do |config|
11
+ attr_accessor config
12
+ end
13
+ attr_reader :logger
14
+
15
+ def initialize()
16
+ @logger = Log4r::Logger.new("[mailer]")
17
+ @logger.add(Log4r::StdoutOutputter.new('console'))
18
+
19
+ @smtp_auth_type ||= :login
20
+ @environment ||= Mailer::ENVIRONMENT[:development]
21
+ end
22
+
23
+ def smtp_username=(value)
24
+ if @default_from.nil? && value && !value.match(/\A([\w\.\-\+]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i).nil?
25
+ @default_from = value
26
+ end
27
+ @smtp_username = value
28
+ end
29
+
30
+ def log_file=(file)
31
+ @logger.add(Log4r::FileOutputter.new('fileOutputter', :filename => file, :trunc => false, :formatter => Log4r::PatternFormatter.new(:pattern => "[%l] %d :: %m"))) #rescue nil
32
+ end
33
+
34
+ def check
35
+ CONFIGS.reject{|c| NOT_REQUIRED.include?(c)}.each do |config|
36
+ raise Mailer::ConfigError, "#{config} not configured." unless instance_variable_get("@#{config}")
37
+ end
38
+ end
39
+
40
+ def development?
41
+ environment.to_s == Mailer::ENVIRONMENT[:development]
42
+ end
43
+ def test?
44
+ environment.to_s == Mailer::ENVIRONMENT[:test]
45
+ end
46
+ def production?
47
+ environment.to_s == Mailer::ENVIRONMENT[:production]
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,14 @@
1
+ require 'mailer/exceptions'
2
+
3
+ # This is just an array of sent tmail Mail objs that Mailer puts sent mails into in test mode
4
+ module Mailer
5
+ class Deliveries < ::Array
6
+
7
+ def initialize(*args)
8
+ super(args)
9
+ end
10
+
11
+ alias_method :latest, :last
12
+
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ module Mailer
2
+
3
+ class MailerError < StandardError
4
+ end
5
+
6
+ class ConfigError < MailerError
7
+ end
8
+
9
+ class SendError < MailerError
10
+ end
11
+
12
+ end
@@ -0,0 +1,90 @@
1
+ module Mailer; end
2
+ module Mailer::ShouldaMacros; end
3
+
4
+ module Mailer::ShouldaMacros::TestUnit
5
+
6
+ protected
7
+
8
+ def should_be_sent_from(*addresses)
9
+ addresses.flatten.each do |address|
10
+ should "should be sent from #{address}" do
11
+ assert(subject.from.include?(address), "mail not sent from #{address}")
12
+ end
13
+ end
14
+ end
15
+
16
+ def should_be_sent_with_reply_to(*addresses)
17
+ addresses.flatten.each do |address|
18
+ should "should be sent with reply to #{address}" do
19
+ assert(subject.reply_to.include?(address), "mail not sent with reply to #{address}")
20
+ end
21
+ end
22
+ end
23
+
24
+ def should_be_sent_to(*addresses)
25
+ addresses.flatten.each do |address|
26
+ should "should be sent to #{address}" do
27
+ assert(subject.to.include?(address), "mail not sent to #{address}")
28
+ end
29
+ end
30
+ end
31
+
32
+ def should_be_sent_cc(*addresses)
33
+ addresses.flatten.each do |address|
34
+ should "should be sent cc #{address}" do
35
+ assert(subject.cc.include?(address), "mail not sent cc #{address}")
36
+ end
37
+ end
38
+ end
39
+
40
+ def should_be_sent_bcc(*addresses)
41
+ addresses.flatten.each do |address|
42
+ should "should be sent bcc #{address}" do
43
+ assert(subject.bcc.include?(address), "mail not sent bcc #{address}")
44
+ end
45
+ end
46
+ end
47
+
48
+ def should_be_sent_with_subject(string)
49
+ should "should be sent with the subject '#{string}'" do
50
+ assert_equal(string, subject.subject, "mail not sent with the subject '#{string}'")
51
+ end
52
+ end
53
+
54
+ def should_be_sent_with_subject_containing(match)
55
+ should "should be sent with the subject containing '#{match}'" do
56
+ assert_match(match, subject.subject, "mail not sent with the subject containing '#{match}'")
57
+ end
58
+ end
59
+
60
+ def should_be_sent_with_body_containing(match)
61
+ should "should be sent with the body containing '#{match}'" do
62
+ assert_match(match, subject.body, "mail not sent with the body containing '#{match}'")
63
+ end
64
+ end
65
+
66
+ def should_be_sent_with_content_type(string)
67
+ should "should be sent with the content type '#{string}'" do
68
+ assert_equal(string, subject.content_type, "mail not sent with the content type '#{string}'")
69
+ end
70
+ end
71
+
72
+ def should_send_mail_with_the_settings
73
+ should "send mail with the settings: #{subject.inspect}" do
74
+ assert_nothing_raised("error while sending with settings: #{subject.inspect}") do
75
+ Mailer.send(subject)
76
+ end
77
+ end
78
+ end
79
+
80
+ def should_not_pass_the_config_check
81
+ should "not pass the config check" do
82
+ assert_raises(Mailer::ConfigError) do
83
+ Mailer.config.check
84
+ end
85
+ end
86
+ end
87
+
88
+ end
89
+
90
+ Test::Unit::TestCase.extend(Mailer::ShouldaMacros::TestUnit) if defined? Test::Unit::TestCase
@@ -0,0 +1,9 @@
1
+ module Mailer; end
2
+
3
+ module Mailer::TestHelpers
4
+
5
+ def latest_sent_mail
6
+ Mailer.deliveries.latest
7
+ end
8
+
9
+ end
data/lib/mailer/tls.rb ADDED
@@ -0,0 +1,73 @@
1
+ # Note: This is a complete rip of http://github.com/ambethia/smtp-tls/tree/master
2
+ # => I chose to copy the source in here instead of add yet another gem depencency
3
+ # => I take no credit for this work, check out the link for more info.
4
+
5
+ require 'net/smtp'
6
+
7
+ Net::SMTP.class_eval do
8
+ private
9
+ def do_start(helodomain, user, secret, authtype)
10
+ raise IOError, 'SMTP session already started' if @started
11
+
12
+ if RUBY_VERSION > "1.8.6"
13
+ check_auth_args user, secret if user or secret
14
+ else
15
+ check_auth_args user, secret, authtype if user or secret
16
+ end
17
+
18
+ sock = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
19
+ @socket = Net::InternetMessageIO.new(sock)
20
+ @socket.read_timeout = 60 #@read_timeout
21
+
22
+ check_response(critical { recv_response() })
23
+ do_helo(helodomain)
24
+
25
+ if starttls
26
+ raise 'openssl library not installed' unless defined?(OpenSSL)
27
+ ssl = OpenSSL::SSL::SSLSocket.new(sock)
28
+ ssl.sync_close = true
29
+ ssl.connect
30
+ @socket = Net::InternetMessageIO.new(ssl)
31
+ @socket.read_timeout = 60 #@read_timeout
32
+ do_helo(helodomain)
33
+ end
34
+
35
+ authenticate user, secret, authtype if user
36
+ @started = true
37
+ ensure
38
+ unless @started
39
+ # authentication failed, cancel connection.
40
+ @socket.close if not @started and @socket and not @socket.closed?
41
+ @socket = nil
42
+ end
43
+ end
44
+
45
+ def do_helo(helodomain)
46
+ begin
47
+ if @esmtp
48
+ ehlo helodomain
49
+ else
50
+ helo helodomain
51
+ end
52
+ rescue Net::ProtocolError
53
+ if @esmtp
54
+ @esmtp = false
55
+ @error_occured = false
56
+ retry
57
+ end
58
+ raise
59
+ end
60
+ end
61
+
62
+ def starttls
63
+ getok('STARTTLS') rescue return false
64
+ return true
65
+ end
66
+
67
+ def quit
68
+ begin
69
+ getok('QUIT')
70
+ rescue EOFError
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,13 @@
1
+ module Mailer
2
+ module Version
3
+
4
+ MAJOR = 0
5
+ MINOR = 1
6
+ TINY = 0
7
+
8
+ def self.to_s # :nodoc:
9
+ [MAJOR, MINOR, TINY].join('.')
10
+ end
11
+
12
+ end
13
+ end
data/lib/mailer.rb ADDED
@@ -0,0 +1,143 @@
1
+ require 'openssl'
2
+ require 'net/smtp'
3
+ require 'tmail'
4
+ require 'log4r'
5
+
6
+ require 'mailer/exceptions'
7
+ require 'mailer/config'
8
+ require 'mailer/deliveries'
9
+ require 'mailer/tls'
10
+
11
+ module Mailer
12
+
13
+ REQUIRED_FIELDS = [:from, :subject]
14
+ ADDRESS_FIELDS = [:to, :cc, :bcc]
15
+ DEFAULT_CONTENT_TYPE = "text/plain"
16
+ DEFAULT_CHARSET = "UTF-8"
17
+
18
+ ENVIRONMENT = {
19
+ :development => 'development',
20
+ :test => 'test',
21
+ :production => 'production'
22
+ }
23
+ def self.development
24
+ ENVIRONMENT[:development]
25
+ end
26
+ def self.test
27
+ ENVIRONMENT[:test]
28
+ end
29
+ def self.production
30
+ ENVIRONMENT[:production]
31
+ end
32
+
33
+ @@config ||= Mailer::Config.new
34
+ def self.configure
35
+ yield @@config
36
+ end
37
+ def self.config
38
+ @@config
39
+ end
40
+
41
+ @@deliveries ||= Mailer::Deliveries.new
42
+ def self.deliveries
43
+ @@deliveries
44
+ end
45
+
46
+ # Macro style helper for sending email based on the Mailer configuration
47
+ def self.send(settings={})
48
+ mail = build_tmail(settings)
49
+ mail.body = yield(mail) if block_given?
50
+ mail.body ||= ''
51
+ deliver_tmail(mail)
52
+ mail
53
+ end
54
+
55
+ # Returns a tmail Mail obj based on a hash of settings
56
+ # => same settings that the .send macro accepts
57
+ def self.build_tmail(some_settings)
58
+ settings = some_settings.dup
59
+ settings[:from] ||= @@config.default_from
60
+ mail = TMail::Mail.new
61
+
62
+ # Defaulted settings
63
+ mail.date = Time.now
64
+ mail.content_type = DEFAULT_CONTENT_TYPE
65
+ mail.charset = DEFAULT_CHARSET
66
+
67
+ # Required settings
68
+ REQUIRED_FIELDS.each {|field| mail.send("#{field}=", settings.delete(field))}
69
+
70
+ # Optional settings
71
+ # => settings "pass thru" to the tmail Mail obj
72
+ settings.each do |field, value|
73
+ mail.send("#{field}=", value) if mail.respond_to?("#{field}=")
74
+ end
75
+
76
+ mail
77
+ end
78
+
79
+ # Deliver a tmail Mail obj depending on configured environment
80
+ # => production?: using Net::SMTP
81
+ # => test?: add to deliveries cache
82
+ # => development?: log mail
83
+ def self.deliver_tmail(mail)
84
+ check_mail(mail)
85
+ @@config.check
86
+ if @@config.production?
87
+ smtp_start_args = [
88
+ @@config.smtp_server,
89
+ @@config.smtp_port,
90
+ @@config.smtp_helo_domain,
91
+ @@config.smtp_username,
92
+ @@config.smtp_password,
93
+ @@config.smtp_auth_type
94
+ ]
95
+ Net::SMTP.start(*smtp_start_args) do |smtp|
96
+ ADDRESS_FIELDS.each do |field|
97
+ if (recipients = mail.send(field))
98
+ recipients.each {|recipient| smtp.send_message(mail.to_s, mail.from, recipient) }
99
+ end
100
+ end
101
+ end
102
+ elsif @@config.test?
103
+ @@deliveries << mail
104
+ end
105
+ log(:info, "Sent '#{mail.subject}' to #{mail.to ? mail.to.join(', ') : "''"} (#{@@config.environment})")
106
+ log_tmail(mail)
107
+ end
108
+
109
+ # Logs a tmail Mail obj delivery
110
+ def self.log_tmail(mail)
111
+ log(:debug, [
112
+ "",
113
+ "====================================================================",
114
+ mail.to_s,
115
+ "====================================================================",
116
+ ""
117
+ ].join("\n"))
118
+ end
119
+
120
+ protected
121
+
122
+ def self.check_mail(tmail)
123
+ raise Mailer::SendError, "cannot send, bad mail object given." unless tmail && tmail.kind_of?(TMail::Mail)
124
+ REQUIRED_FIELDS.each do |field|
125
+ raise Mailer::SendError, "cannot send, #{field} not specified." unless tmail.send(field)
126
+ end
127
+ # be sure at least one of ADDRESS_FIELDS exists
128
+ unless ADDRESS_FIELDS.inject(false) {|exist, field| (exist || !tmail.send(field).nil?)}
129
+ raise Mailer::SendError, "cannot send, no #{ADDRESS_FIELDS.join('/')} specified."
130
+ end
131
+ end
132
+
133
+ def self.log(level, msg)
134
+ if(msg)
135
+ if !@@config.production? && MAILER_LOG_AS_PUTS
136
+ puts "[#{level.to_s.upcase}]: [mailer] #{msg}" if Mailer.config.development?
137
+ elsif @@config.logger && @@config.logger.respond_to?(level)
138
+ @@config.logger.send(level.to_s, msg)
139
+ end
140
+ end
141
+ end
142
+
143
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kelredd-mailer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kelly Redding
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-06 00:00:00 -06:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: log4r
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: tmail
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.2.3.0
34
+ version:
35
+ description:
36
+ email: kelly@kelredd.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.rdoc
43
+ files:
44
+ - README.rdoc
45
+ - Rakefile
46
+ - lib/mailer/config.rb
47
+ - lib/mailer/deliveries.rb
48
+ - lib/mailer/exceptions.rb
49
+ - lib/mailer/shoulda_macros/test_unit.rb
50
+ - lib/mailer/test_helpers.rb
51
+ - lib/mailer/tls.rb
52
+ - lib/mailer/version.rb
53
+ - lib/mailer.rb
54
+ has_rdoc: true
55
+ homepage: http://github.com/kelredd/mailer
56
+ licenses: []
57
+
58
+ post_install_message:
59
+ rdoc_options:
60
+ - --main
61
+ - README.rdoc
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ version:
76
+ requirements: []
77
+
78
+ rubyforge_project:
79
+ rubygems_version: 1.3.5
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: This gem is just a simple mailer interface.
83
+ test_files: []
84
+