tuktuk 0.1.0

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.
@@ -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
+