bulkmail 0.1 → 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.
@@ -0,0 +1,35 @@
1
+
2
+ <?xml version="1.0" encoding="iso-8859-1"?>
3
+ <!DOCTYPE html
4
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
5
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
6
+
7
+ <!--
8
+
9
+ Methods
10
+
11
+ -->
12
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
13
+ <head>
14
+ <title>Methods</title>
15
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
16
+ <link rel="stylesheet" href="rdoc-style.css" type="text/css" />
17
+ <base target="docwin" />
18
+ </head>
19
+ <body>
20
+ <div id="index">
21
+ <h1 class="section-bar">Methods</h1>
22
+ <div id="index-entries">
23
+ <a href="classes/String.html#M000009">email_addr (String)</a><br />
24
+ <a href="classes/BulkMail.html#M000002">format_headers (BulkMail)</a><br />
25
+ <a href="classes/BulkMail.html#M000005">send_bulk (BulkMail)</a><br />
26
+ <a href="classes/BulkMail.html#M000004">send_message (BulkMail)</a><br />
27
+ <a href="classes/BulkMail.html#M000001">server_settings (BulkMail)</a><br />
28
+ <a href="classes/BulkMail.html#M000003">smtp (BulkMail)</a><br />
29
+ <a href="classes/Array.html#M000007">split (Array)</a><br />
30
+ <a href="classes/String.html#M000008">to_header (String)</a><br />
31
+ <a href="classes/Symbol.html#M000006">to_header (Symbol)</a><br />
32
+ </div>
33
+ </div>
34
+ </body>
35
+ </html>
@@ -0,0 +1,24 @@
1
+ <?xml version="1.0" encoding="iso-8859-1"?>
2
+ <!DOCTYPE html
3
+ PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
4
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
5
+
6
+ <!--
7
+
8
+ Bulkmail Documentation
9
+
10
+ -->
11
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
12
+ <head>
13
+ <title>Bulkmail Documentation</title>
14
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
15
+ </head>
16
+ <frameset rows="20%, 80%">
17
+ <frameset cols="25%,35%,45%">
18
+ <frame src="fr_file_index.html" title="Files" name="Files" />
19
+ <frame src="fr_class_index.html" name="Classes" />
20
+ <frame src="fr_method_index.html" name="Methods" />
21
+ </frameset>
22
+ <frame src="files/README.html" name="docwin" />
23
+ </frameset>
24
+ </html>
@@ -0,0 +1,208 @@
1
+
2
+ body {
3
+ font-family: Verdana,Arial,Helvetica,sans-serif;
4
+ font-size: 90%;
5
+ margin: 0;
6
+ margin-left: 40px;
7
+ padding: 0;
8
+ background: white;
9
+ }
10
+
11
+ h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; }
12
+ h1 { font-size: 150%; }
13
+ h2,h3,h4 { margin-top: 1em; }
14
+
15
+ a { background: #eef; color: #039; text-decoration: none; }
16
+ a:hover { background: #039; color: #eef; }
17
+
18
+ /* Override the base stylesheet's Anchor inside a table cell */
19
+ td > a {
20
+ background: transparent;
21
+ color: #039;
22
+ text-decoration: none;
23
+ }
24
+
25
+ /* and inside a section title */
26
+ .section-title > a {
27
+ background: transparent;
28
+ color: #eee;
29
+ text-decoration: none;
30
+ }
31
+
32
+ /* === Structural elements =================================== */
33
+
34
+ div#index {
35
+ margin: 0;
36
+ margin-left: -40px;
37
+ padding: 0;
38
+ font-size: 90%;
39
+ }
40
+
41
+
42
+ div#index a {
43
+ margin-left: 0.7em;
44
+ }
45
+
46
+ div#index .section-bar {
47
+ margin-left: 0px;
48
+ padding-left: 0.7em;
49
+ background: #ccc;
50
+ font-size: small;
51
+ }
52
+
53
+
54
+ div#classHeader, div#fileHeader {
55
+ width: auto;
56
+ color: white;
57
+ padding: 0.5em 1.5em 0.5em 1.5em;
58
+ margin: 0;
59
+ margin-left: -40px;
60
+ border-bottom: 3px solid #006;
61
+ }
62
+
63
+ div#classHeader a, div#fileHeader a {
64
+ background: inherit;
65
+ color: white;
66
+ }
67
+
68
+ div#classHeader td, div#fileHeader td {
69
+ background: inherit;
70
+ color: white;
71
+ }
72
+
73
+
74
+ div#fileHeader {
75
+ background: #057;
76
+ }
77
+
78
+ div#classHeader {
79
+ background: #048;
80
+ }
81
+
82
+
83
+ .class-name-in-header {
84
+ font-size: 180%;
85
+ font-weight: bold;
86
+ }
87
+
88
+
89
+ div#bodyContent {
90
+ padding: 0 1.5em 0 1.5em;
91
+ }
92
+
93
+ div#description {
94
+ padding: 0.5em 1.5em;
95
+ background: #efefef;
96
+ border: 1px dotted #999;
97
+ }
98
+
99
+ div#description h1,h2,h3,h4,h5,h6 {
100
+ color: #125;;
101
+ background: transparent;
102
+ }
103
+
104
+ div#validator-badges {
105
+ text-align: center;
106
+ }
107
+ div#validator-badges img { border: 0; }
108
+
109
+ div#copyright {
110
+ color: #333;
111
+ background: #efefef;
112
+ font: 0.75em sans-serif;
113
+ margin-top: 5em;
114
+ margin-bottom: 0;
115
+ padding: 0.5em 2em;
116
+ }
117
+
118
+
119
+ /* === Classes =================================== */
120
+
121
+ table.header-table {
122
+ color: white;
123
+ font-size: small;
124
+ }
125
+
126
+ .type-note {
127
+ font-size: small;
128
+ color: #DEDEDE;
129
+ }
130
+
131
+ .xxsection-bar {
132
+ background: #eee;
133
+ color: #333;
134
+ padding: 3px;
135
+ }
136
+
137
+ .section-bar {
138
+ color: #333;
139
+ border-bottom: 1px solid #999;
140
+ margin-left: -20px;
141
+ }
142
+
143
+
144
+ .section-title {
145
+ background: #79a;
146
+ color: #eee;
147
+ padding: 3px;
148
+ margin-top: 2em;
149
+ margin-left: -30px;
150
+ border: 1px solid #999;
151
+ }
152
+
153
+ .top-aligned-row { vertical-align: top }
154
+ .bottom-aligned-row { vertical-align: bottom }
155
+
156
+ /* --- Context section classes ----------------------- */
157
+
158
+ .context-row { }
159
+ .context-item-name { font-family: monospace; font-weight: bold; color: black; }
160
+ .context-item-value { font-size: small; color: #448; }
161
+ .context-item-desc { color: #333; padding-left: 2em; }
162
+
163
+ /* --- Method classes -------------------------- */
164
+ .method-detail {
165
+ background: #efefef;
166
+ padding: 0;
167
+ margin-top: 0.5em;
168
+ margin-bottom: 1em;
169
+ border: 1px dotted #ccc;
170
+ }
171
+ .method-heading {
172
+ color: black;
173
+ background: #ccc;
174
+ border-bottom: 1px solid #666;
175
+ padding: 0.2em 0.5em 0 0.5em;
176
+ }
177
+ .method-signature { color: black; background: inherit; }
178
+ .method-name { font-weight: bold; }
179
+ .method-args { font-style: italic; }
180
+ .method-description { padding: 0 0.5em 0 0.5em; }
181
+
182
+ /* --- Source code sections -------------------- */
183
+
184
+ a.source-toggle { font-size: 90%; }
185
+ div.method-source-code {
186
+ background: #262626;
187
+ color: #ffdead;
188
+ margin: 1em;
189
+ padding: 0.5em;
190
+ border: 1px dashed #999;
191
+ overflow: hidden;
192
+ }
193
+
194
+ div.method-source-code pre { color: #ffdead; overflow: hidden; }
195
+
196
+ /* --- Ruby keyword styles --------------------- */
197
+
198
+ .standalone-code { background: #221111; color: #ffdead; overflow: hidden; }
199
+
200
+ .ruby-constant { color: #7fffd4; background: transparent; }
201
+ .ruby-keyword { color: #00ffff; background: transparent; }
202
+ .ruby-ivar { color: #eedd82; background: transparent; }
203
+ .ruby-operator { color: #00ffee; background: transparent; }
204
+ .ruby-identifier { color: #ffdead; background: transparent; }
205
+ .ruby-node { color: #ffa07a; background: transparent; }
206
+ .ruby-comment { color: #b22222; font-weight: bold; background: transparent; }
207
+ .ruby-regexp { color: #ffa07a; background: transparent; }
208
+ .ruby-value { color: #7fffd4; background: transparent; }
@@ -1,75 +1,110 @@
1
- require 'action_mailer'
1
+ require 'net/smtp'
2
2
 
3
- # The BulkMail module contains the Job class, which does the actual
4
- # bulk-mailing, and a convenience method to start a job.
5
- module BulkMail
6
- class Mailer < ActionMailer::Base #:nodoc
7
- def msg(addr, sub, content, from_addr, c_type, additional_headers)
8
- recipients addr
9
- subject sub if sub
10
- body content
11
- from from_addr if from_addr
12
- content_type c_type if c_type
13
- headers additional_headers if additional_headers
14
- end
3
+ # Symbol extensions
4
+ class Symbol
5
+ # Returns the symbol as a header string
6
+ def to_header
7
+ to_s.split('_').map{|w| w.capitalize}.join('-')
8
+ end
9
+ end
10
+
11
+ class String
12
+ # Returns self
13
+ def to_header
14
+ self
15
+ end
16
+
17
+ # Returns the address part of a recipient
18
+ def email_addr
19
+ s = strip
20
+ return nil if s.empty?
21
+ s =~ /<(.*)>/ ? $1 : s
15
22
  end
23
+ end
16
24
 
17
- # The Job class performs the bulk-mailing work. Each instance starts a thread
18
- # That goes over the list of recipients and sends each one an email. A job
19
- # can also be queried for its progress, returning a number between 0 and 1.
20
- class Job
21
- attr_accessor :progress, :errors, :thread, :delivered, :total
22
-
23
- def initialize(opts)
24
- @recipients = IO.readlines(opts[:recipients_file])
25
- @subject = opts[:subject]
26
- @from = opts[:from]
27
- @content_type = opts[:content_type]
28
- @content = load_content(opts[:content_file])
29
- @progress = 0
30
- @delivered = 0
31
- @total = @recipients.size
32
- @errors = []
33
- @thread = Thread.new {process}
25
+ class Array
26
+ # Splits the array into smaller arrays of fixed size
27
+ def split(batch)
28
+ result = []
29
+ idx = 0
30
+ while idx < size
31
+ result << slice(idx, batch)
32
+ idx += batch
34
33
  end
34
+ result
35
+ end
36
+ end
35
37
 
36
- def load_content(fn)
37
- content = IO.readlines(fn)
38
- while line = content.shift
39
- break if line.empty?
40
- if line =~ /^(.+):\s?(.*)$/
41
- @headers ||= {}
42
- @headers[$1] = $2
43
- else
44
- content.unshift(line)
45
- break
46
- end
47
- end
48
- content.join
38
+ class BulkMail
39
+ @@server_settings = {
40
+ :host => 'localhost',
41
+ :port => 25,
42
+ :helo_domain => nil,
43
+ :user => nil,
44
+ :password => nil,
45
+ :auth => :plain
46
+ }
47
+
48
+ # Returns the server_settings hash
49
+ def self.server_settings
50
+ @@server_settings
51
+ end
52
+
53
+ LINE_BREAK = "\r\n"
54
+ HEADER_FMT = "%s: %s\r\n".freeze
55
+
56
+ # Returns the message headers formatted
57
+ def self.format_headers(headers)
58
+ headers.inject('') do |m, kv|
59
+ m << (HEADER_FMT % [kv[0].to_header, kv[1]])
49
60
  end
50
-
51
- def process
52
- @recipients.each_with_index do |addr, idx|
53
- begin
54
- Mailer.deliver_msg(addr, @subject, @content, @from, @content_type, @headers)
55
- @progress = idx / @recipients.size
56
- @delivered += 1
57
- puts "Sent email to #{addr}"
58
- rescue => e
59
- puts e.message
60
- @errors << e
61
- end
62
- end
63
- @progress = 1
61
+ end
62
+
63
+ # Starts an SMTP session and yields
64
+ def self.smtp
65
+ Net::SMTP.start(
66
+ @@server_settings[:host],
67
+ @@server_settings[:port],
68
+ @@server_settings[:helo_domain] || @@server_settings[:host],
69
+ @@server_settings[:user],
70
+ @@server_settings[:password],
71
+ @@server_settings[:auth]) do |smtp|
72
+ yield smtp
64
73
  end
65
74
  end
66
75
 
67
- def self.send(opts)
68
- Job.new(opts)
76
+ # Sends an email
77
+ def self.send_message(smtp, from, to, body, headers = nil)
78
+ if headers && !headers.empty?
79
+ msg = format_headers(headers)
80
+ else
81
+ msg = LINE_BREAK
82
+ end
83
+ msg << LINE_BREAK
84
+ msg << body
85
+ smtp.send_message msg, from, to
69
86
  end
70
87
 
71
- def self.server_settings
72
- ActionMailer::Base.server_settings
88
+ # Sends bulk mail to a large number of recipients.
89
+ def self.send_bulk(sender, recipients, body, headers)
90
+ failed = []
91
+ succeeded = []
92
+ headers[:from] = headers[:to] = sender
93
+ recipients = recipients.map {|r| r.email_addr}.compact
94
+ recipients.split(10).each do |list|
95
+ begin
96
+ smtp {|s| send_message(s, sender.email_addr, list, body, headers)}
97
+ succeeded.concat list
98
+ puts "Sent message to #{list.join(', ')}"
99
+ rescue => e
100
+ failed.concat list
101
+ puts "Failed to send to #{list.join(', ')}: #{e.message}\r\n#{e.backtrace.first}"
102
+ end
103
+ end
104
+ # log failed addresses
105
+ unless failed.empty?
106
+ File.open('failed.bulkmail', 'w') {|f| failed.each {|l|f.puts l}}
107
+ end
108
+ puts "Message to sent to #{succeeded.size} of #{recipients.size} recipients"
73
109
  end
74
110
  end
75
-
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: bulkmail
5
5
  version: !ruby/object:Gem::Version
6
- version: "0.1"
7
- date: 2006-10-18 00:00:00 +02:00
6
+ version: "0.2"
7
+ date: 2006-10-27 00:00:00 +02:00
8
8
  summary: Ruby bulk-mailer.
9
9
  require_paths:
10
10
  - lib
@@ -33,6 +33,24 @@ files:
33
33
  - README
34
34
  - Rakefile
35
35
  - bin/bulkmail
36
+ - doc/rdoc
37
+ - doc/rdoc/classes
38
+ - doc/rdoc/created.rid
39
+ - doc/rdoc/files
40
+ - doc/rdoc/fr_class_index.html
41
+ - doc/rdoc/fr_file_index.html
42
+ - doc/rdoc/fr_method_index.html
43
+ - doc/rdoc/index.html
44
+ - doc/rdoc/rdoc-style.css
45
+ - doc/rdoc/classes/Array.html
46
+ - doc/rdoc/classes/BulkMail.html
47
+ - doc/rdoc/classes/String.html
48
+ - doc/rdoc/classes/Symbol.html
49
+ - doc/rdoc/files/CHANGELOG.html
50
+ - doc/rdoc/files/COPYING.html
51
+ - doc/rdoc/files/lib
52
+ - doc/rdoc/files/README.html
53
+ - doc/rdoc/files/lib/bulkmail_rb.html
36
54
  - lib/bulkmail.rb
37
55
  - CHANGELOG
38
56
  test_files: []
@@ -61,13 +79,5 @@ extensions: []
61
79
 
62
80
  requirements: []
63
81
 
64
- dependencies:
65
- - !ruby/object:Gem::Dependency
66
- name: actionmailer
67
- version_requirement:
68
- version_requirements: !ruby/object:Gem::Version::Requirement
69
- requirements:
70
- - - ">"
71
- - !ruby/object:Gem::Version
72
- version: 0.0.0
73
- version:
82
+ dependencies: []
83
+