system_mail 0.0.2
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 +17 -0
- data/.travis.yml +4 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +55 -0
- data/Rakefile +9 -0
- data/lib/system_mail/message.rb +251 -0
- data/lib/system_mail/storage.rb +59 -0
- data/lib/system_mail/version.rb +3 -0
- data/lib/system_mail.rb +17 -0
- data/system_mail.gemspec +23 -0
- data/test/fixtures/test.html +1 -0
- data/test/minitest_helper.rb +2 -0
- data/test/system_mail/system_mail-storage_test.rb +53 -0
- data/test/system_mail/system_mail_test.rb +1 -0
- metadata +99 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Igor Bochkariov
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
[](https://travis-ci.org/ujifgc/system_mail)
|
2
|
+
[](https://codeclimate.com/github/ujifgc/system_mail)
|
3
|
+
|
4
|
+
## Introduction
|
5
|
+
|
6
|
+
SystemMail is a Ruby library built to compose and deliver internet mail using
|
7
|
+
operating system utilities.
|
8
|
+
|
9
|
+
SystemMail features:
|
10
|
+
|
11
|
+
* tiny memory footprint even with big attachments
|
12
|
+
* blazing-fast gem loading and message composing
|
13
|
+
* alternating message body format: text, enriched, HTML
|
14
|
+
* rich capabilities in attaching files
|
15
|
+
* ability to combine HTML message with file attachments
|
16
|
+
|
17
|
+
Operating system commands used to do the job are:
|
18
|
+
|
19
|
+
* `sendmail -t < temp` or alternative sends the message to Mail Transfer Agent
|
20
|
+
* `base64 file >> temp` encodes binary files to textual form
|
21
|
+
* `file --mime-type --mime-encoding -b file` detects Content-Type and charset
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
|
25
|
+
Add this line to your application's Gemfile:
|
26
|
+
|
27
|
+
gem 'system_mail'
|
28
|
+
|
29
|
+
And then execute:
|
30
|
+
|
31
|
+
$ bundle
|
32
|
+
|
33
|
+
Or install it yourself as:
|
34
|
+
|
35
|
+
$ gem install system_mail
|
36
|
+
|
37
|
+
## Usage
|
38
|
+
|
39
|
+
mail = SystemMail.new(
|
40
|
+
from: 'user@example.com',
|
41
|
+
to: ['user1@gmail.com', 'user2@gmail.com'],
|
42
|
+
subject: 'test проверочный subject',
|
43
|
+
files: ['Gemfile', 'Gemfile.lock'],
|
44
|
+
text: 'big small норм',
|
45
|
+
html: File.read('test.html')
|
46
|
+
)
|
47
|
+
mail.deliver
|
48
|
+
|
49
|
+
## Contributing
|
50
|
+
|
51
|
+
1. Fork it
|
52
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
53
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
54
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
55
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,251 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'shellwords'
|
3
|
+
require 'system_mail/storage'
|
4
|
+
|
5
|
+
module SystemMail
|
6
|
+
##
|
7
|
+
# Manages compiling an email message from various attributes and files.
|
8
|
+
#
|
9
|
+
class Message
|
10
|
+
BASE64_SIZE = 76
|
11
|
+
UTF8_SIZE = 998
|
12
|
+
SETTINGS = {
|
13
|
+
:sendmail => '/usr/sbin/sendmail -t',
|
14
|
+
:base64 => 'base64',
|
15
|
+
:file => 'file --mime-type --mime-encoding -b',
|
16
|
+
:storage => ENV['TMP'] || '/tmp',
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
##
|
20
|
+
# Creates new message. Available options:
|
21
|
+
#
|
22
|
+
# - :text, String, Textual part of the message
|
23
|
+
# - :enriched, String, Enriched alternative of the message (RFC 1896)
|
24
|
+
# - :html, String, HTML alternative of the message
|
25
|
+
# - :from, String, 'From:' header for the message
|
26
|
+
# - :to, String or Array of Strings, 'To:' header, if Arrey, it gets joined by ', '
|
27
|
+
# - :subject, String, Subject of the message, it gets encoded automatically
|
28
|
+
# - :files, File or String of file path or Array of them, Attachments of the message
|
29
|
+
#
|
30
|
+
# Options :text, :enriched and :html are interchangeable.
|
31
|
+
# Option :to is required.
|
32
|
+
#
|
33
|
+
# Examples:
|
34
|
+
#
|
35
|
+
# mail = Message.new(
|
36
|
+
# :from => 'user@example.com',
|
37
|
+
# :to => 'user@gmail.com',
|
38
|
+
# :subject => 'test subject',
|
39
|
+
# :text => 'big small normal',
|
40
|
+
# :html => File.read('test.html'),
|
41
|
+
# :attachments => [File.open('Gemfile'), 'attachment.zip'],
|
42
|
+
# )
|
43
|
+
#
|
44
|
+
def initialize(options={})
|
45
|
+
@body = {}
|
46
|
+
@to = []
|
47
|
+
@mutex = Mutex.new
|
48
|
+
%W(text enriched html from to subject attachments).each do |option|
|
49
|
+
name = option.to_sym
|
50
|
+
send(name, options[name])
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Delivers the message using sendmail.
|
56
|
+
#
|
57
|
+
# Example:
|
58
|
+
#
|
59
|
+
# mail.deliver #=> nil
|
60
|
+
#
|
61
|
+
def deliver
|
62
|
+
validate
|
63
|
+
with_storage do
|
64
|
+
write_headers
|
65
|
+
write_message
|
66
|
+
send_message
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def settings
|
71
|
+
@settings ||= SETTINGS.dup
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def text(input)
|
77
|
+
@body['text'] = input
|
78
|
+
end
|
79
|
+
|
80
|
+
def enriched(input)
|
81
|
+
@body['enriched'] = input
|
82
|
+
end
|
83
|
+
|
84
|
+
def html(input)
|
85
|
+
@body['html'] = input
|
86
|
+
end
|
87
|
+
|
88
|
+
def from(input)
|
89
|
+
@from = input
|
90
|
+
end
|
91
|
+
|
92
|
+
def to(input)
|
93
|
+
@to += Array(input)
|
94
|
+
end
|
95
|
+
|
96
|
+
def subject(input)
|
97
|
+
@subject = input
|
98
|
+
end
|
99
|
+
|
100
|
+
def attachments(input)
|
101
|
+
input && input.each{ |file| add_file(*file) }
|
102
|
+
end
|
103
|
+
|
104
|
+
def add_file(name, path = nil)
|
105
|
+
path ||= name
|
106
|
+
name = File.basename(name)
|
107
|
+
files[name] = path
|
108
|
+
end
|
109
|
+
|
110
|
+
def files
|
111
|
+
@files ||= {}
|
112
|
+
end
|
113
|
+
|
114
|
+
def with_storage
|
115
|
+
@mutex.synchronize do
|
116
|
+
@storage = Storage.new settings[:storage]
|
117
|
+
yield
|
118
|
+
@storage.clear
|
119
|
+
@storage = nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def write_message
|
124
|
+
if files.any?
|
125
|
+
multipart :mixed do |boundary|
|
126
|
+
write_part boundary
|
127
|
+
write_body
|
128
|
+
files.each_pair do |name, path|
|
129
|
+
write_part boundary
|
130
|
+
write_file name, path
|
131
|
+
end
|
132
|
+
end
|
133
|
+
else
|
134
|
+
write_body
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def write_body
|
139
|
+
case @body.length
|
140
|
+
when 0
|
141
|
+
nil
|
142
|
+
when 1
|
143
|
+
data, type = @body.first
|
144
|
+
write_content data, "text/#{type}"
|
145
|
+
else
|
146
|
+
multipart :alternative do |boundary|
|
147
|
+
%w[text enriched html].each do |type|
|
148
|
+
data = @body[type] || next
|
149
|
+
write_part boundary
|
150
|
+
write_content data, "text/#{type}"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def write_content(data, content_type)
|
157
|
+
if data.bytesize < UTF8_SIZE || !data.lines.any?{ |line| line.bytesize > UTF8_SIZE }
|
158
|
+
write_8bit_data data, content_type
|
159
|
+
else
|
160
|
+
write_base64_data data, content_type
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def multipart(type)
|
165
|
+
boundary = new_boundary(type)
|
166
|
+
@storage.puts "Content-Type: multipart/#{type}; boundary=\"#{boundary}\""
|
167
|
+
yield boundary
|
168
|
+
write_part boundary, :end
|
169
|
+
end
|
170
|
+
|
171
|
+
def write_headers
|
172
|
+
@storage.puts "From: #{@from}" if @from
|
173
|
+
@storage.puts "To: #{@to.join(', ')}"
|
174
|
+
@storage.puts "Subject: #{encode_subject}"
|
175
|
+
end
|
176
|
+
|
177
|
+
def write_part(boundary, type = :begin)
|
178
|
+
@storage.puts
|
179
|
+
@storage.puts "--#{boundary}#{type == :end ? '--' : ''}"
|
180
|
+
end
|
181
|
+
|
182
|
+
def write_base64_data(data, content_type)
|
183
|
+
@storage.puts "Content-Type: #{content_type}; charset=#{data.encoding}"
|
184
|
+
@storage.puts "Content-Transfer-Encoding: base64"
|
185
|
+
@storage.puts
|
186
|
+
Base64.strict_encode64(data).each_slice(BASE64_SIZE) do |line|
|
187
|
+
@storage.puts line
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def write_8bit_data(data, content_type)
|
192
|
+
@storage.puts "Content-Type: #{content_type}; charset=#{data.encoding}"
|
193
|
+
@storage.puts "Content-Transfer-Encoding: 8bit"
|
194
|
+
@storage.puts
|
195
|
+
@storage.puts data
|
196
|
+
end
|
197
|
+
|
198
|
+
def write_file(name, file)
|
199
|
+
path = case file
|
200
|
+
when String
|
201
|
+
fail Errno::ENOENT, file unless File.file?(file)
|
202
|
+
fail Errno::EACCES, file unless File.readable?(file)
|
203
|
+
file
|
204
|
+
when File
|
205
|
+
file.path
|
206
|
+
else
|
207
|
+
fail ArgumentError, 'attachment must be File or String of file path'
|
208
|
+
end
|
209
|
+
write_base64_file name, path
|
210
|
+
end
|
211
|
+
|
212
|
+
def write_base64_file(name, path)
|
213
|
+
@storage.puts "Content-Type: #{read_mime(path)}"
|
214
|
+
@storage.puts "Content-Transfer-Encoding: base64"
|
215
|
+
@storage.puts "Content-Disposition: attachment; filename=\"#{name}\""
|
216
|
+
@storage.puts
|
217
|
+
@storage.capture do |message_path|
|
218
|
+
`#{settings[:base64]} '#{path.shellescape}' >> #{message_path}`
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def send_message
|
223
|
+
if @storage.file?
|
224
|
+
@storage.capture do |message_path|
|
225
|
+
`#{settings[:sendmail]} < #{message_path}`
|
226
|
+
end
|
227
|
+
else
|
228
|
+
IO.popen(settings[:sendmail]) do |io|
|
229
|
+
io.write(@storage.read)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def read_mime(file_path)
|
235
|
+
`#{settings[:file]} '#{file_path.shellescape}'`.strip
|
236
|
+
end
|
237
|
+
|
238
|
+
def validate
|
239
|
+
fail ArgumentError, "Header 'To:' is empty" if @to.empty?
|
240
|
+
warn 'Message body is empty' if @body.empty?
|
241
|
+
end
|
242
|
+
|
243
|
+
def new_boundary(type)
|
244
|
+
rand(36**6).to_s(36).rjust(20,type.to_s)
|
245
|
+
end
|
246
|
+
|
247
|
+
def encode_subject
|
248
|
+
@subject.ascii_only? ? @subject : "=?UTF-8?B?#{Base64.strict_encode64 @subject}?="
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module SystemMail
|
5
|
+
##
|
6
|
+
# A class to store string data either in StringIO or in Tempfile.
|
7
|
+
#
|
8
|
+
class Storage
|
9
|
+
def initialize(path = nil)
|
10
|
+
@tmpdir = path || Dir.tmpdir
|
11
|
+
@io = StringIO.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def puts(data = nil)
|
15
|
+
@io.puts(data)
|
16
|
+
end
|
17
|
+
|
18
|
+
def read
|
19
|
+
if file?
|
20
|
+
capture do |file_path|
|
21
|
+
File.read file_path
|
22
|
+
end
|
23
|
+
else
|
24
|
+
@io.string.dup
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def capture
|
29
|
+
ensure_tempfile
|
30
|
+
@io.close
|
31
|
+
yield @io.path
|
32
|
+
@io.open
|
33
|
+
end
|
34
|
+
|
35
|
+
def file?
|
36
|
+
@io.kind_of?(Tempfile)
|
37
|
+
end
|
38
|
+
|
39
|
+
def clear
|
40
|
+
@io.close
|
41
|
+
@io.unlink if file?
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def ensure_tempfile
|
47
|
+
return if file?
|
48
|
+
tempfile = create_tempfile
|
49
|
+
tempfile.puts @io.string if @io.size > 0
|
50
|
+
@io = tempfile
|
51
|
+
end
|
52
|
+
|
53
|
+
def create_tempfile
|
54
|
+
temp_directory = File.join(@tmpdir, 'system_mail')
|
55
|
+
FileUtils.mkdir_p(temp_directory)
|
56
|
+
Tempfile.new('storage', temp_directory, :mode => IO::APPEND)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/system_mail.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'system_mail/version'
|
2
|
+
require 'system_mail/message'
|
3
|
+
|
4
|
+
module SystemMail
|
5
|
+
def self.new(options = {}, &block)
|
6
|
+
message = Message.new(options)
|
7
|
+
message.instance_eval(&block) if block_given?
|
8
|
+
message
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.email(options = {}, &block)
|
12
|
+
message = SystemMail.new(options, &block)
|
13
|
+
message.deliver
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Mail = SystemMail
|
data/system_mail.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'system_mail/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "system_mail"
|
8
|
+
spec.version = SystemMail::VERSION
|
9
|
+
spec.authors = ["Igor Bochkariov"]
|
10
|
+
spec.email = ["ujifgc@gmail.com"]
|
11
|
+
spec.description = 'A Ruby library built to compose and deliver internet mail using operating system utilities.'
|
12
|
+
spec.summary = 'SystemMail is a blazing-fast Ruby Mail alternative with tiny memory footprint.'
|
13
|
+
spec.homepage = "https://github.com/ujifgc/system_mail"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<small>small</small> <big>big</big> нормал
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#coding:utf-8
|
2
|
+
require 'minitest_helper'
|
3
|
+
|
4
|
+
describe SystemMail::Storage do
|
5
|
+
before do
|
6
|
+
SystemMail::Storage.class_eval{ attr_accessor :io }
|
7
|
+
@fixture_path = File.expand_path('../fixtures/test.html', File.dirname(__FILE__))
|
8
|
+
@fixture = File.read @fixture_path
|
9
|
+
@s = SystemMail::Storage.new
|
10
|
+
end
|
11
|
+
|
12
|
+
after do
|
13
|
+
@s.clear
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should properly initialize' do
|
17
|
+
assert_kind_of StringIO, @s.io
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should properly write strings' do
|
21
|
+
2.times do
|
22
|
+
@s.puts 'line1'
|
23
|
+
@s.puts 'а также линия'
|
24
|
+
@s.puts
|
25
|
+
end
|
26
|
+
assert_equal "line1\nа также линия\n\n"*2, @s.io.string
|
27
|
+
assert_kind_of StringIO, @s.io
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should properly write and append files with commandline' do
|
31
|
+
2.times do
|
32
|
+
@s.capture do |path|
|
33
|
+
`cat '#{@fixture_path}' >> '#{path}'`
|
34
|
+
end
|
35
|
+
end
|
36
|
+
assert_equal @fixture*2, File.read(@s.io.path)
|
37
|
+
assert_kind_of Tempfile, @s.io
|
38
|
+
assert_match /system_mail(.*)storage/, @s.io.path
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should properly mix strings and files' do
|
42
|
+
2.times do
|
43
|
+
@s.puts 'line1'
|
44
|
+
@s.puts 'а также линия'
|
45
|
+
@s.puts
|
46
|
+
@s.capture do |path|
|
47
|
+
File.open(path, 'a') { |f| f.write @fixture }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
assert_equal ("line1\nа также линия\n\n"+@fixture)*2, File.read(@s.io.path)
|
51
|
+
assert_kind_of Tempfile, @s.io
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'minitest_helper'
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: system_mail
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Igor Bochkariov
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-01-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.3'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.3'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: A Ruby library built to compose and deliver internet mail using operating
|
47
|
+
system utilities.
|
48
|
+
email:
|
49
|
+
- ujifgc@gmail.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- .travis.yml
|
56
|
+
- Gemfile
|
57
|
+
- LICENSE.txt
|
58
|
+
- README.md
|
59
|
+
- Rakefile
|
60
|
+
- lib/system_mail.rb
|
61
|
+
- lib/system_mail/message.rb
|
62
|
+
- lib/system_mail/storage.rb
|
63
|
+
- lib/system_mail/version.rb
|
64
|
+
- system_mail.gemspec
|
65
|
+
- test/fixtures/test.html
|
66
|
+
- test/minitest_helper.rb
|
67
|
+
- test/system_mail/system_mail-storage_test.rb
|
68
|
+
- test/system_mail/system_mail_test.rb
|
69
|
+
homepage: https://github.com/ujifgc/system_mail
|
70
|
+
licenses:
|
71
|
+
- MIT
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ! '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
requirements: []
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 1.8.25
|
91
|
+
signing_key:
|
92
|
+
specification_version: 3
|
93
|
+
summary: SystemMail is a blazing-fast Ruby Mail alternative with tiny memory footprint.
|
94
|
+
test_files:
|
95
|
+
- test/fixtures/test.html
|
96
|
+
- test/minitest_helper.rb
|
97
|
+
- test/system_mail/system_mail-storage_test.rb
|
98
|
+
- test/system_mail/system_mail_test.rb
|
99
|
+
has_rdoc:
|