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.
- data/.gitignore +1 -0
- data/Gemfile +4 -0
- data/Rakefile +2 -0
- data/lib/tuktuk.rb +1 -0
- data/lib/tuktuk/package.rb +72 -0
- data/lib/tuktuk/tuktuk.rb +118 -0
- data/lib/tuktuk/version.rb +7 -0
- data/tuktuk.gemspec +24 -0
- metadata +121 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*~
|
data/Gemfile
ADDED
data/Rakefile
ADDED
data/lib/tuktuk.rb
ADDED
@@ -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
|
data/tuktuk.gemspec
ADDED
@@ -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
|
+
|