tuktuk 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ *~
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :rubygems
2
+
3
+ # specified in tuktuk.gemspec
4
+ gemspec
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1 @@
1
+ require 'tuktuk/tuktuk'
@@ -0,0 +1,72 @@
1
+ require 'mail'
2
+
3
+ module Package
4
+
5
+ class << self
6
+
7
+ def new(message)
8
+ mail = message[:html_body] ? mixed(message) : plain(message)
9
+ mail.charset = 'UTF-8'
10
+
11
+ mail['In-Reply-To'] = message[:in_reply_to] if message[:in_reply_to]
12
+ mail['List-Archive'] = message[:list_archive] if message[:list_archive] # https://github.com/tomas/prey
13
+ mail['List-Id'] = message[:list_id] if message[:list_id] # <prey.tomas.github.com>
14
+
15
+ if message[:return_path]
16
+ mail['Return-Path'] = message[:return_path]
17
+ mail['Bounces-To'] = message[:return_path]
18
+ mail['Errors-To'] = message[:return_path]
19
+ end
20
+
21
+ mail
22
+ end
23
+
24
+ def plain(message)
25
+ mail = Mail.new do
26
+ from message[:from]
27
+ to message[:to]
28
+ reply_to message[:reply_to] if message[:reply_to]
29
+ # sender message[:sender] if message[:sender]
30
+ subject message[:subject]
31
+ message_id message[:message_id] if message[:message_id]
32
+ body message[:attachments] ? link_plain_attachments(message[:attachments], message[:body]) : message[:body]
33
+ end
34
+ end
35
+
36
+ def mixed(message)
37
+ mail = Mail.new do
38
+ from message[:from]
39
+ to message[:to]
40
+ reply_to message[:reply_to] if message[:reply_to]
41
+ # sender message[:sender] if message[:sender]
42
+ subject message[:subject]
43
+ message_id message[:message_id] if message[:message_id]
44
+ text_part do
45
+ body message[:attachments] ? link_plain_attachments(message[:attachments], message[:body]) : message[:body]
46
+ end
47
+ html_part do
48
+ content_type 'text/html; charset=UTF-8'
49
+ body message[:attachments] ? link_html_attachments(message[:attachments], message[:html_body]) : message[:html_body]
50
+ end
51
+ end
52
+ end
53
+
54
+ def link_plain_attachments(attachments, body)
55
+ text = "\n\nAttachments\n==========\n"
56
+ attachments.each_with_index do |att, i|
57
+ text += "[#{att[:name]}] #{att[:link]} \n"
58
+ end
59
+ body + text
60
+ end
61
+
62
+ def link_html_attachments(attachments, body)
63
+ text = "\n\n<h4>Attachments</h4>\n<ul>\n"
64
+ attachments.each_with_index do |att, i|
65
+ text += "<li>[<a href='#{att[:link]}' target='_blank'>#{att[:name]}</a>] <a href='#{att[:link]}' target='_blank'>#{att[:link]}</a></li>\n"
66
+ end
67
+ body + text + "</ul>"
68
+ end
69
+
70
+ end
71
+
72
+ end
@@ -0,0 +1,118 @@
1
+ require 'net/smtp'
2
+ require 'net/dns/resolver'
3
+ require 'dkim'
4
+ require 'logger'
5
+ require 'tuktuk/package'
6
+
7
+ DEFAULTS = {
8
+ :retry_sleep => 10,
9
+ :max_attempts => 3
10
+ }
11
+
12
+ module Tuktuk
13
+
14
+ class << self
15
+
16
+ def deliver(message, opts = {})
17
+ config.merge(opts)
18
+ mail = Package.new(message)
19
+ mail['X-Mailer'] = opts[:smtp_server_name] || "Tuktuk SMTP #{VERSION}"
20
+ lookup_and_deliver(mail)
21
+ mail
22
+ end
23
+
24
+ def dkim=(dkim_opts)
25
+ Dkim::domain = dkim_opts[:domain]
26
+ Dkim::selector = dkim_opts[:selector]
27
+ Dkim::private_key = dkim_opts[:private_key]
28
+ end
29
+
30
+ private
31
+
32
+ def config
33
+ @config ||= DEFAULTS
34
+ end
35
+
36
+ def use_dkim?
37
+ !Dkim::domain.nil?
38
+ end
39
+
40
+ def logger
41
+ @logger ||= Logger.new(config[:logfile])
42
+ end
43
+
44
+ def get_domain(email_address)
45
+ email_address[/@([a-z0-9\._-]+)/i, 1]
46
+ end
47
+
48
+ def success(destination)
49
+ logger.info("#{destination} - Successfully sent mail!")
50
+ end
51
+
52
+ def error(mail, destination, error, attempt = 1)
53
+ if attempt <= config[:max_attempts] && (error.is_a?(Net::SMTPServerBusy) or error.is_a?(EOFError))
54
+ logger.info "#{destination} - Got #{error.class.name} error. Retrying after #{config[:retry_sleep]} secs..."
55
+ sleep config[:retry_sleep]
56
+ lookup_and_deliver(mail, attempt+1)
57
+ else
58
+ error_message = error.respond_to?(:message) ? "#{error.message} [#{error.class.name}]" : error
59
+ logger.error("#{destination} - Unable to send: #{error_message}")
60
+ raise "Unable to send to #{destination}: #{error_message}"
61
+ end
62
+ end
63
+
64
+ def smtp_servers_for_domain(domain)
65
+ res = Net::DNS::Resolver.new
66
+ if mx = res.mx(domain)
67
+ mx.sort {|x,y| x.preference <=> y.preference}.map {|rr| rr.exchange}
68
+ else
69
+ raise RuntimeError, "Could not locate MX records for domain #{domain}."
70
+ end
71
+ end
72
+
73
+ def lookup_and_deliver(mail, attempt = 1)
74
+ raise "No destinations found! Forgot to pass the to: field?" if mail.destinations.empty?
75
+
76
+ mail.destinations.each do |destination|
77
+
78
+ domain = get_domain(destination)
79
+ servers = smtp_servers_for_domain(domain)
80
+ error(mail, destination, "Unknown host: #{domain}") && next if servers.empty?
81
+
82
+ last_error = nil
83
+ servers.each do |server|
84
+ begin
85
+ send_now(mail, server, destination)
86
+ break
87
+ rescue => e
88
+ last_error = e
89
+ end
90
+ end
91
+ error(mail, destination, last_error, attempt) if last_error
92
+ end
93
+ end
94
+
95
+ def send_now(mail, server, to)
96
+ raw_mail = use_dkim? ? Dkim.sign(mail.to_s).to_s : mail.to_s
97
+
98
+ from = mail.return_path || mail.sender || mail.from_addrs.first
99
+ logger.info "#{to} - Delivering email at #{server}..."
100
+
101
+ context = OpenSSL::SSL::SSLContext.new
102
+ context.verify_mode = OpenSSL::SSL::VERIFY_NONE # OpenSSL::SSL::VERIFY_PEER
103
+
104
+ domain = config[:domain] || get_domain(to)
105
+
106
+ smtp = Net::SMTP.new(server, nil)
107
+ smtp.enable_starttls_auto(context)
108
+ smtp.start(domain, nil, nil, nil) do |smtp|
109
+ result = smtp.send_message(raw_mail, from, to)
110
+ logger.info res.string
111
+ end
112
+
113
+ success(to)
114
+ end
115
+
116
+ end
117
+
118
+ end
@@ -0,0 +1,7 @@
1
+ module Tuktuk
2
+ MAJOR = 0
3
+ MINOR = 1
4
+ PATCH = 0
5
+
6
+ VERSION = [MAJOR, MINOR, PATCH].join('.')
7
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path("../lib/tuktuk/version", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "tuktuk"
6
+ s.version = Tuktuk::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ['Tomás Pollak']
9
+ s.email = ['tomas@forkhq.com']
10
+ s.homepage = "https://github.com/tomas/tuktuk"
11
+ s.summary = "SMTP client for Ruby with DKIM support."
12
+ s.description = "An easy way of sending plain or multipart mails directly from Ruby with DKIM support."
13
+
14
+ s.required_rubygems_version = ">= 1.3.6"
15
+ s.rubyforge_project = "tuktuk"
16
+
17
+ s.add_development_dependency "bundler", ">= 1.0.0"
18
+ s.add_runtime_dependency "mail", "~> 2.3"
19
+ s.add_runtime_dependency "dkim", "~> 0.0.2"
20
+
21
+ s.files = `git ls-files`.split("\n")
22
+ s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
23
+ s.require_path = 'lib'
24
+ end
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tuktuk
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - "Tom\xC3\xA1s Pollak"
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-05-30 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: bundler
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 23
29
+ segments:
30
+ - 1
31
+ - 0
32
+ - 0
33
+ version: 1.0.0
34
+ type: :development
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: mail
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 5
45
+ segments:
46
+ - 2
47
+ - 3
48
+ version: "2.3"
49
+ type: :runtime
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: dkim
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ~>
58
+ - !ruby/object:Gem::Version
59
+ hash: 27
60
+ segments:
61
+ - 0
62
+ - 0
63
+ - 2
64
+ version: 0.0.2
65
+ type: :runtime
66
+ version_requirements: *id003
67
+ description: An easy way of sending plain or multipart mails directly from Ruby with DKIM support.
68
+ email:
69
+ - tomas@forkhq.com
70
+ executables: []
71
+
72
+ extensions: []
73
+
74
+ extra_rdoc_files: []
75
+
76
+ files:
77
+ - .gitignore
78
+ - Gemfile
79
+ - Rakefile
80
+ - lib/tuktuk.rb
81
+ - lib/tuktuk/package.rb
82
+ - lib/tuktuk/tuktuk.rb
83
+ - lib/tuktuk/version.rb
84
+ - tuktuk.gemspec
85
+ homepage: https://github.com/tomas/tuktuk
86
+ licenses: []
87
+
88
+ post_install_message:
89
+ rdoc_options: []
90
+
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ hash: 3
99
+ segments:
100
+ - 0
101
+ version: "0"
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ hash: 23
108
+ segments:
109
+ - 1
110
+ - 3
111
+ - 6
112
+ version: 1.3.6
113
+ requirements: []
114
+
115
+ rubyforge_project: tuktuk
116
+ rubygems_version: 1.8.15
117
+ signing_key:
118
+ specification_version: 3
119
+ summary: SMTP client for Ruby with DKIM support.
120
+ test_files: []
121
+