mlist 0.1.9
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/CHANGELOG +59 -0
- data/README +204 -0
- data/Rakefile +27 -0
- data/TODO +36 -0
- data/VERSION.yml +4 -0
- data/lib/mlist/email.rb +69 -0
- data/lib/mlist/email_post.rb +126 -0
- data/lib/mlist/email_server/base.rb +33 -0
- data/lib/mlist/email_server/default.rb +31 -0
- data/lib/mlist/email_server/fake.rb +16 -0
- data/lib/mlist/email_server/pop.rb +28 -0
- data/lib/mlist/email_server/smtp.rb +24 -0
- data/lib/mlist/email_server.rb +2 -0
- data/lib/mlist/email_subscriber.rb +6 -0
- data/lib/mlist/list.rb +183 -0
- data/lib/mlist/mail_list.rb +277 -0
- data/lib/mlist/manager/database.rb +48 -0
- data/lib/mlist/manager/notifier.rb +31 -0
- data/lib/mlist/manager.rb +30 -0
- data/lib/mlist/message.rb +150 -0
- data/lib/mlist/server.rb +62 -0
- data/lib/mlist/thread.rb +98 -0
- data/lib/mlist/util/email_helpers.rb +155 -0
- data/lib/mlist/util/header_sanitizer.rb +71 -0
- data/lib/mlist/util/quoting.rb +70 -0
- data/lib/mlist/util/tmail_builder.rb +42 -0
- data/lib/mlist/util/tmail_methods.rb +138 -0
- data/lib/mlist/util.rb +12 -0
- data/lib/mlist.rb +46 -0
- data/lib/pop_ssl.rb +999 -0
- data/rails/init.rb +22 -0
- data/spec/fixtures/schema.rb +94 -0
- data/spec/integration/date_formats_spec.rb +12 -0
- data/spec/integration/mlist_spec.rb +232 -0
- data/spec/integration/pop_email_server_spec.rb +22 -0
- data/spec/integration/proof_spec.rb +74 -0
- data/spec/matchers/equal_tmail.rb +53 -0
- data/spec/matchers/have_address.rb +48 -0
- data/spec/matchers/have_header.rb +104 -0
- data/spec/models/email_post_spec.rb +100 -0
- data/spec/models/email_server/base_spec.rb +11 -0
- data/spec/models/email_spec.rb +54 -0
- data/spec/models/mail_list_spec.rb +469 -0
- data/spec/models/message_spec.rb +109 -0
- data/spec/models/thread_spec.rb +83 -0
- data/spec/models/util/email_helpers_spec.rb +47 -0
- data/spec/models/util/header_sanitizer_spec.rb +19 -0
- data/spec/models/util/quoting_spec.rb +96 -0
- data/spec/spec_helper.rb +76 -0
- metadata +103 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
*0.1.9 [Enhancement] (2009-12-21)
|
2
|
+
|
3
|
+
* DomainKey-Signature and DKIM-Signature headers will not be published to list so that sending SMTP servers may sign. [aiwilliams]
|
4
|
+
* Deploying to http://gemcutter.org [aiwilliams]
|
5
|
+
|
6
|
+
*0.1.8 [Enhancement] (2009-08-19)
|
7
|
+
|
8
|
+
* return-receipt-to header will not be published to list to avoid having recipients spam list with receipts [aiwilliams]
|
9
|
+
|
10
|
+
*0.1.7 [Bug Fixes, Delivery Improvements] (2009-08-14)
|
11
|
+
|
12
|
+
* is maintained as spacing in html_to_text conversions [aiwilliams]
|
13
|
+
* Fixed bug where delivered email included the original Cc header. This could cause all kinds of problems. [aiwilliams]
|
14
|
+
* Fixed bug where list addresses in Cc header were not be utilized in determining the lists to deliver to. [aiwilliams]
|
15
|
+
* Fixed bug where delivery date was left to the mercy of the Rails time configuration. Using Time.now. [aiwilliams]
|
16
|
+
* Manager list can indicate if reply-to should be list address or subscriber. [aiwilliams]
|
17
|
+
* Leaving the reply-to field intact when it already exists on incoming email. [aiwilliams]
|
18
|
+
* Messages will not be delivered to addresses found in the TO and CC fields in order to keep those recipients from getting two emails. [aiwilliams]
|
19
|
+
* Added ability to optionally copy sender. [aiwilliams]
|
20
|
+
|
21
|
+
*0.1.6 [Bug Fixes] (March 5, 2009)
|
22
|
+
|
23
|
+
* Messages are processed even when there are no recipients [aiwilliams]
|
24
|
+
* Escaping DQUOTE and \ in email address phrase for some headers [aiwilliams]
|
25
|
+
|
26
|
+
*0.1.5 [Solid Basics] (January 17, 2009)
|
27
|
+
|
28
|
+
* No longer storing the list label in the message subject field, thereby supporting cleaner viewing of threads [aiwilliams]
|
29
|
+
* Improved handling of incoming email subjects by leaving labels alone that aren't obviously the label of the list, thereby allowing for subjects like "[Ann] My New Thing" [aiwilliams]
|
30
|
+
* Notifying subscribers when the list indicates that they are currently blocked from posting messages to a list they are subscribed to [aiwilliams]
|
31
|
+
* Added MList::Manager module to better define the interface of list managers. This needs to be included into list manager implementations. [aiwilliams]
|
32
|
+
* MList::List implementors may now answer the footer content to be appended to the bottom of the text/plain part of messages. [aiwilliams]
|
33
|
+
* List footers are stripped from text/plain part of messages before being delivered. [aiwilliams]
|
34
|
+
* Observers of MList models which are defined in client applications now work without special instruction. [aiwilliams]
|
35
|
+
* A first pass implementation for converting html to text using Hpricot. [aiwilliams]
|
36
|
+
* Better thread tree, supporting message navigation within a thread through a linked list kind of approach. [aiwilliams]
|
37
|
+
* Better parent message associating using in-reply-to, then references, then subject. [aiwilliams]
|
38
|
+
* MList.version is hash of {:major => 0, :minor => 0, :patch => 0}, with a to_s of 'MList 0.0.0'. [aiwilliams]
|
39
|
+
* Fixed bug where original email source content was last in TMail::Mail#to_s usage. [aiwilliams]
|
40
|
+
|
41
|
+
*0.1.4 [] (January 7, 2009)
|
42
|
+
|
43
|
+
* Fixed bug where default email server was not allowing for settings [aiwilliams]
|
44
|
+
* Made subject for reply place 're:' in front of the list label [aiwilliams]
|
45
|
+
* Added really simple tree support. Really simple. [aiwilliams]
|
46
|
+
|
47
|
+
*0.1.3 [] (January 7, 2009)
|
48
|
+
|
49
|
+
* Generating message id as UUID [aiwilliams]
|
50
|
+
* Allowing setting of domain for message id [aiwilliams]
|
51
|
+
* Fixed bug in storing message id [aiwilliams]
|
52
|
+
|
53
|
+
*0.1.2 [] (January 7, 2009)
|
54
|
+
|
55
|
+
* Added references header when creating a reply post. [aiwilliams]
|
56
|
+
* Including display_name in from address of EmailPost. [aiwilliams]
|
57
|
+
* Improved extraction of text when it is nested inside parts. [aiwilliams]
|
58
|
+
|
59
|
+
*0.1.1 [First Working Release] (January 5, 2009)
|
data/README
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
= MList
|
2
|
+
|
3
|
+
== DESCRIPTION:
|
4
|
+
|
5
|
+
An insane attempt to build a mail list server library that can be used in other
|
6
|
+
applications very easily. The first target is Ruby applications that can load
|
7
|
+
MList models for direct, embedded integration. It will later have a RESTful API
|
8
|
+
so that any language/architecture can be easily integrated. That may depend
|
9
|
+
heavily on community involvement...
|
10
|
+
|
11
|
+
== FEATURES/PROBLEMS:
|
12
|
+
|
13
|
+
If you have any experience with mailing lists, things can be understood most
|
14
|
+
quickly with this: MList is the mailing list server and database, your
|
15
|
+
application is the list manager.
|
16
|
+
|
17
|
+
MList is being used in a production environment as a Ruby gem/Rails plugin. I
|
18
|
+
decided not to use other list software primarily because a) we already had a
|
19
|
+
running, sophisticated list manager that I didn't care to synchronize with
|
20
|
+
something else, b) we have an exceptional UI design for listing messages in
|
21
|
+
threads and ultimately will have search and c) integration options for the other
|
22
|
+
software looked to be a problem in themselves.
|
23
|
+
|
24
|
+
And then there's the fame and glory that comes with building something like
|
25
|
+
this...
|
26
|
+
|
27
|
+
There is a LOT to do: segmenting, i18n, backscatter - only the Mailman developers
|
28
|
+
know what else. I have enough experience to know that rewrites are NEVER as easy
|
29
|
+
as they seem. Alas, I go boldly forward.
|
30
|
+
|
31
|
+
==== Thread Trees
|
32
|
+
|
33
|
+
A Thread answers a tree of messages where each message in the tree knows it's
|
34
|
+
parent, children, previous and next message, and whether it is the root or a leaf
|
35
|
+
in the tree. The messages are actually delegates, wrappers around the real
|
36
|
+
message instances from the thread.
|
37
|
+
|
38
|
+
This approach makes a few assumptions:
|
39
|
+
|
40
|
+
# You want the tree because you will be displaying it # Moving through a tree
|
41
|
+
message by message should 'feel' right; next is determined by walking the tree
|
42
|
+
depth first.
|
43
|
+
|
44
|
+
It may or may not prove useful someday to have this knowledge in the form of
|
45
|
+
something like awesome_nested_set.
|
46
|
+
|
47
|
+
==== Extracting 'from' IP address from emails is not implemented
|
48
|
+
|
49
|
+
http://compnetworking.about.com/od/workingwithipaddresses/qt/ipaddressemail.htm
|
50
|
+
|
51
|
+
==== Deleting messages
|
52
|
+
|
53
|
+
Mail list servers don't typically deal with this, do they? When an email is sent,
|
54
|
+
it's sent! If you delete one, how can a reply to it be accurately injected into a
|
55
|
+
partially broken tree?
|
56
|
+
|
57
|
+
Since MList is designed to integrate with applications that want to distribute
|
58
|
+
messages, those applications may want to delete messages. I feel, at the time of
|
59
|
+
writing this, that the feature of deleting is NOT a problem that MList should
|
60
|
+
attempt to address in great detail. The models are ActiveRecord descendants, so
|
61
|
+
they can be deleted with #destroy, et. al. MList will not prevent that from
|
62
|
+
happening. This has implications, of course.
|
63
|
+
|
64
|
+
# MList::Thread#tree will likely break if messages are gone.
|
65
|
+
|
66
|
+
In my own usage, I have opted for adding a column to my mlist_messages table,
|
67
|
+
deleted_at. The application will make decisions based on whether that column has
|
68
|
+
a value or not. I would suggest you implement something similar, always favoring
|
69
|
+
leaving the records in place (paranoid delete). Since MList::MailList#messages
|
70
|
+
(and other such has_many associations) don't know about this column, they will
|
71
|
+
always answer collections which still include those messages.
|
72
|
+
|
73
|
+
When an MList::MailList is destroyed, all it's MList::Messages and MList::Threads
|
74
|
+
will be deleted (:dependent => :delete_all). If no other MList::Messages are
|
75
|
+
referencing the MList::Email records, they will also be deleted.
|
76
|
+
|
77
|
+
==== Ensuring Delivery
|
78
|
+
|
79
|
+
The internet email system is a mess. Spam has proven to be a significant
|
80
|
+
problem which plagues both your inbox and the service providers who work to
|
81
|
+
deliver email into it. So, what must you, the user of MList do, to ensure that
|
82
|
+
your software plays nice, and your emails are delivered?
|
83
|
+
|
84
|
+
Of course, this problem goes well beyond MList - any email you send from your
|
85
|
+
application servers will suffer the consequences of a misconfigured domain.
|
86
|
+
Here's what I've learned:
|
87
|
+
|
88
|
+
* Go to http://old.openspf.org/wizard.html and create your SPF record
|
89
|
+
content, to be placed in a TXT record for your domain.
|
90
|
+
- If you are using Google Apps, check this out:
|
91
|
+
http://www.google.com/support/a/bin/answer.py?hl=en&answer=33786
|
92
|
+
* Make sure you have an abuse@yourdomain.com and postmaster@yourdomain.com
|
93
|
+
address.
|
94
|
+
- If you are using Google Apps, you will need to create 'groups' for those
|
95
|
+
addresses, adding yourself as a recipient. You can read more about it
|
96
|
+
here: http://blog.wordtothewise.com/2009/01/google-apps-wheres-my-abuse/
|
97
|
+
* Be prepared to visit the Feedback Loop (FBL) configuration pages of many
|
98
|
+
ISPs, like the one at AOL: http://postmaster.aol.com/fbl/
|
99
|
+
* You'll probably want to have a way to resolve an email address (subscriber)
|
100
|
+
to an IP address from which the subscriber subscribed. This will help your
|
101
|
+
case should you have an ISP rejecting your mail.
|
102
|
+
|
103
|
+
There is a great article here:
|
104
|
+
|
105
|
+
http://community-support.engineyard.com/faqs/guides/making-sure-your-email-gets-delivered
|
106
|
+
|
107
|
+
Although that is EY focused, it gives you a good idea of some of the things
|
108
|
+
you should get right.
|
109
|
+
|
110
|
+
== SYNOPSIS:
|
111
|
+
|
112
|
+
Let's say you want your web application to have a mailing list feature. Let's
|
113
|
+
also say you care about the UI, and you don't want to learn all about creating
|
114
|
+
the correct HTML structures for a mailing list. You want to have lots of power
|
115
|
+
for searching the mail, and you have your own strategy for managing the lists.
|
116
|
+
You love Ruby. You want MList.
|
117
|
+
|
118
|
+
== REQUIREMENTS:
|
119
|
+
|
120
|
+
You'll need some gems.
|
121
|
+
|
122
|
+
* hpricot
|
123
|
+
* uuid (macaddr also)
|
124
|
+
* tmail
|
125
|
+
* active_support
|
126
|
+
* active_record
|
127
|
+
|
128
|
+
== INSTALL:
|
129
|
+
|
130
|
+
- gem sources add http://gemcutter.org
|
131
|
+
- sudo gem install mlist
|
132
|
+
|
133
|
+
For now, copy the mlist_ tables from the spec/fixtures/schema.rb file and move
|
134
|
+
them to a new migration in your application. Run the migration.
|
135
|
+
|
136
|
+
Now you'll need to create the MList::Server and provide it with a list manager
|
137
|
+
and MList::EmailServer::Default instance. Something like this in your
|
138
|
+
environment.rb after the initialize block (our gem needs to have been loaded):
|
139
|
+
|
140
|
+
Rails::Initializer.run do |config|
|
141
|
+
# Please do specify a version, and check for the latest!
|
142
|
+
config.gem "mlist", :version => '0.1.0'
|
143
|
+
end
|
144
|
+
|
145
|
+
MLIST_SERVER = MList::Server.new(
|
146
|
+
:list_manager => MList::Manager::Database.new,
|
147
|
+
:email_server => MList::EmailServer::Default.new(
|
148
|
+
MList::EmailServer::Pop.new(
|
149
|
+
:ssl => false,
|
150
|
+
:server => 'pop.gmail.com',
|
151
|
+
:port => '995',
|
152
|
+
:username => 'yourusername',
|
153
|
+
:password => 'yourpassword'
|
154
|
+
),
|
155
|
+
MList::EmailServer::Smtp.new(
|
156
|
+
ActionMailer::Base.smtp_settings # probably good enough!
|
157
|
+
)
|
158
|
+
)
|
159
|
+
)
|
160
|
+
|
161
|
+
Your list manager needs to implement only two methods. Check out (and use if you
|
162
|
+
like) the MList::Manager::Database for more information.
|
163
|
+
|
164
|
+
You'll need something to trigger the incoming server process. Take your pick from
|
165
|
+
http://wiki.rubyonrails.org/rails/pages/HowToRunBackgroundJobsInRails. In the
|
166
|
+
end, if you don't write your own incoming server thing and go with the POP GMail
|
167
|
+
example above, your background process will "MLIST_SERVER.email_server.execute".
|
168
|
+
|
169
|
+
Take a look at MList::EmailPost if you're building a UI. It can be posted to a
|
170
|
+
list something like this:
|
171
|
+
|
172
|
+
class MyList # instances of these are given by your list manager
|
173
|
+
def post(attributes)
|
174
|
+
email = MList::EmailPost.new({
|
175
|
+
:mailer => 'MyApplication'
|
176
|
+
}.merge(attributes))
|
177
|
+
raise 'You can validate these' unless email.valid?
|
178
|
+
message = MLIST_SERVER.mail_list(self).post(email)
|
179
|
+
# do what you will with message. it's already saved.
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
== LICENSE:
|
184
|
+
|
185
|
+
(The MIT License)
|
186
|
+
|
187
|
+
Copyright (c) 2008-2009 Adam Williams (aiwilliams)
|
188
|
+
|
189
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
190
|
+
this software and associated documentation files (the 'Software'), to deal in the
|
191
|
+
Software without restriction, including without limitation the rights to use,
|
192
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
193
|
+
Software, and to permit persons to whom the Software is furnished to do so,
|
194
|
+
subject to the following conditions:
|
195
|
+
|
196
|
+
The above copyright notice and this permission notice shall be included in all
|
197
|
+
copies or substantial portions of the Software.
|
198
|
+
|
199
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
200
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
201
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
202
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
203
|
+
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
204
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'spec/rake/spectask'
|
5
|
+
|
6
|
+
task :default => :spec
|
7
|
+
|
8
|
+
desc "Run all specs"
|
9
|
+
Spec::Rake::SpecTask.new do |t|
|
10
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
11
|
+
t.spec_opts = ['--options', 'spec/spec.opts']
|
12
|
+
end
|
13
|
+
|
14
|
+
begin
|
15
|
+
require 'jeweler'
|
16
|
+
Jeweler::Tasks.new do |s|
|
17
|
+
s.name = 'mlist'
|
18
|
+
s.summary = 'A Ruby mailing list library designed to be integrated into other applications.'
|
19
|
+
s.email = 'adam@thewilliams.ws'
|
20
|
+
s.files = FileList["[A-Z]*", "{lib,rails}/**/*"].exclude("tmp")
|
21
|
+
s.homepage = "http://github.com/aiwilliams/mlist"
|
22
|
+
s.description = s.summary
|
23
|
+
s.authors = ['Adam Williams']
|
24
|
+
end
|
25
|
+
rescue LoadError
|
26
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
27
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#html_to_text
|
2
|
+
|
3
|
+
ADDRESS - Address
|
4
|
+
BLOCKQUOTE - Block quotation
|
5
|
+
|
6
|
+
DIV - Generic block-level container
|
7
|
+
DL - Definition list
|
8
|
+
FIELDSET - Form control group
|
9
|
+
FORM - Interactive form
|
10
|
+
H1 - Level-one heading
|
11
|
+
H2 - Level-two heading
|
12
|
+
H3 - Level-three heading
|
13
|
+
H4 - Level-four heading
|
14
|
+
H5 - Level-five heading
|
15
|
+
H6 - Level-six heading
|
16
|
+
HR - Horizontal rule
|
17
|
+
OL - Ordered list
|
18
|
+
P - Paragraph
|
19
|
+
PRE - Preformatted text
|
20
|
+
|
21
|
+
TABLE - Table
|
22
|
+
output TRs as lines, csv the TDs
|
23
|
+
|
24
|
+
UL - Unordered list
|
25
|
+
|
26
|
+
DD - Definition description
|
27
|
+
DT - Definition term
|
28
|
+
LI - List item
|
29
|
+
|
30
|
+
|
31
|
+
a (Links) - Link to Somewhere[1] ---- [1] http://
|
32
|
+
b (Bold) - *bold*
|
33
|
+
i (Italic) - _italic_
|
34
|
+
strong - *strong*
|
35
|
+
em (emphasis) - _emphasis_
|
36
|
+
u (underline) - probably do nothing, maybe something like em
|
data/VERSION.yml
ADDED
data/lib/mlist/email.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
module MList
|
2
|
+
|
3
|
+
class Email < ActiveRecord::Base
|
4
|
+
set_table_name 'mlist_emails'
|
5
|
+
|
6
|
+
include MList::Util::EmailHelpers
|
7
|
+
include MList::Util::TMailReaders
|
8
|
+
|
9
|
+
def been_here?(list)
|
10
|
+
tmail.header_string('x-beenthere') == list.address
|
11
|
+
end
|
12
|
+
|
13
|
+
def date
|
14
|
+
if date_from_email = super
|
15
|
+
return date_from_email
|
16
|
+
else
|
17
|
+
self.created_at ||= Time.now
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def from
|
22
|
+
tmail.header_string('from')
|
23
|
+
end
|
24
|
+
|
25
|
+
# Answers the usable destination addresses of the email.
|
26
|
+
#
|
27
|
+
def list_addresses
|
28
|
+
bounce? ? tmail.header_string('to').match(/\Amlist-(.*)\Z/)[1] : recipient_addresses
|
29
|
+
end
|
30
|
+
|
31
|
+
# Answers true if this email is a bounce.
|
32
|
+
#
|
33
|
+
# TODO Delegate to the email_server's bounce detector.
|
34
|
+
#
|
35
|
+
def bounce?
|
36
|
+
tmail.header_string('to') =~ /mlist-/
|
37
|
+
end
|
38
|
+
|
39
|
+
def tmail=(tmail)
|
40
|
+
@tmail = tmail
|
41
|
+
write_attribute(:source, tmail.port.read_all)
|
42
|
+
end
|
43
|
+
|
44
|
+
def tmail
|
45
|
+
@tmail ||= TMail::Mail.parse(source)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Provide reader delegation to *most* of the underlying TMail::Mail
|
49
|
+
# methods, excluding those overridden by this Class and the [] method (an
|
50
|
+
# ActiveRecord method).
|
51
|
+
def method_missing(symbol, *args, &block) # :nodoc:
|
52
|
+
if symbol.to_s !~ /=\Z/ && symbol != :[] && symbol != :source && tmail.respond_to?(symbol)
|
53
|
+
tmail.__send__(symbol, *args, &block)
|
54
|
+
else
|
55
|
+
super
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Answers the set of addresses found in the TO and CC fields of the email.
|
60
|
+
#
|
61
|
+
def recipient_addresses
|
62
|
+
(Array(tmail.to) + Array(tmail.cc)).collect(&:downcase).uniq
|
63
|
+
end
|
64
|
+
|
65
|
+
def respond_to?(method)
|
66
|
+
super || (method.to_s !~ /=\Z/ && tmail.respond_to?(method))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module MList
|
2
|
+
|
3
|
+
# The simplest post that can be made to an MList::MailList. Every instance
|
4
|
+
# must have at least the text content and a subject. Html may also be added.
|
5
|
+
#
|
6
|
+
# It is important to understand that this class is intended to be used by
|
7
|
+
# applications that have some kind of UI for creating a post. It assumes
|
8
|
+
# Rails form builder support is desired, and that there is no need for
|
9
|
+
# manipulating the final TMail::Mail object that will be delivered to the
|
10
|
+
# list outside of the methods provided herein.
|
11
|
+
#
|
12
|
+
class EmailPost
|
13
|
+
include MList::Util::EmailHelpers
|
14
|
+
|
15
|
+
ATTRIBUTE_NAMES = %w(copy_sender html text mailer subject subscriber)
|
16
|
+
ATTRIBUTE_NAMES.each do |attribute_name|
|
17
|
+
define_method(attribute_name) do
|
18
|
+
@attributes[attribute_name]
|
19
|
+
end
|
20
|
+
define_method("#{attribute_name}=") do |value|
|
21
|
+
@attributes[attribute_name] = value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :parent_identifier, :reply_to_message
|
26
|
+
|
27
|
+
def initialize(attributes)
|
28
|
+
@attributes = {}
|
29
|
+
self.attributes = {
|
30
|
+
:mailer => 'MList Client Application'
|
31
|
+
}.merge(attributes)
|
32
|
+
end
|
33
|
+
|
34
|
+
def attributes
|
35
|
+
@attributes.dup
|
36
|
+
end
|
37
|
+
|
38
|
+
def attributes=(new_attributes)
|
39
|
+
return if new_attributes.nil?
|
40
|
+
attributes = new_attributes.dup
|
41
|
+
attributes.stringify_keys!
|
42
|
+
attributes.each do |attribute_name, value|
|
43
|
+
send("#{attribute_name}=", value)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def copy_sender=(value)
|
48
|
+
@attributes['copy_sender'] = %w(true 1).include?(value.to_s)
|
49
|
+
end
|
50
|
+
|
51
|
+
def reply_to_message=(message)
|
52
|
+
if message
|
53
|
+
@parent_identifier = message.identifier
|
54
|
+
else
|
55
|
+
@parent_identifier = nil
|
56
|
+
end
|
57
|
+
@reply_to_message = message
|
58
|
+
end
|
59
|
+
|
60
|
+
def subject
|
61
|
+
@attributes['subject'] || (reply_to_message ? "Re: #{reply_to_message.subject}" : nil)
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_s
|
65
|
+
to_tmail.to_s
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_tmail
|
69
|
+
raise ActiveRecord::RecordInvalid.new(self) unless valid?
|
70
|
+
|
71
|
+
builder = MList::Util::TMailBuilder.new(TMail::Mail.new)
|
72
|
+
|
73
|
+
builder.mime_version = "1.0"
|
74
|
+
builder.mailer = mailer
|
75
|
+
|
76
|
+
if parent_identifier
|
77
|
+
builder.in_reply_to = parent_identifier
|
78
|
+
builder.references = [bracket(parent_identifier)]
|
79
|
+
end
|
80
|
+
|
81
|
+
builder.from = subscriber_name_and_address(subscriber)
|
82
|
+
builder.subject = subject
|
83
|
+
|
84
|
+
if html
|
85
|
+
builder.add_text_part(text)
|
86
|
+
builder.add_html_part(html)
|
87
|
+
builder.set_content_type('multipart/alternative')
|
88
|
+
else
|
89
|
+
builder.body = text
|
90
|
+
builder.set_content_type('text/plain')
|
91
|
+
end
|
92
|
+
|
93
|
+
builder.tmail
|
94
|
+
end
|
95
|
+
|
96
|
+
# vvv ActiveRecord validations interface implementation vvv
|
97
|
+
|
98
|
+
def self.human_name(options = {})
|
99
|
+
self.name.humanize
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.self_and_descendants_from_active_record #nodoc:
|
103
|
+
[self]
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.human_attribute_name(attribute_key_name, options = {})
|
107
|
+
attribute_key_name.humanize
|
108
|
+
end
|
109
|
+
|
110
|
+
def errors
|
111
|
+
@errors ||= ActiveRecord::Errors.new(self)
|
112
|
+
end
|
113
|
+
|
114
|
+
def validate
|
115
|
+
errors.clear
|
116
|
+
errors.add(:subject, 'required') if subject.blank?
|
117
|
+
errors.add(:text, 'required') if text.blank?
|
118
|
+
errors.add(:text, 'needs to be a bit longer') if !text.blank? && text.strip.size < 25
|
119
|
+
errors.empty?
|
120
|
+
end
|
121
|
+
|
122
|
+
def valid?
|
123
|
+
validate
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module MList
|
2
|
+
module EmailServer
|
3
|
+
class Base
|
4
|
+
attr_reader :settings
|
5
|
+
|
6
|
+
def initialize(settings)
|
7
|
+
@settings = {
|
8
|
+
:domain => ::Socket.gethostname
|
9
|
+
}.merge(settings)
|
10
|
+
|
11
|
+
@uuid = UUID.new
|
12
|
+
@receivers = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def deliver(tmail)
|
16
|
+
raise 'Implement actual delivery mechanism in subclasses'
|
17
|
+
end
|
18
|
+
|
19
|
+
def generate_message_id
|
20
|
+
"#{@uuid.generate}@#{@settings[:domain]}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def receive(tmail)
|
24
|
+
email = MList::Email.new(:tmail => tmail)
|
25
|
+
@receivers.each { |r| r.receive_email(email) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def receiver(rx)
|
29
|
+
@receivers << rx
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module MList
|
2
|
+
module EmailServer
|
3
|
+
|
4
|
+
class Default < Base
|
5
|
+
def initialize(incoming_server, outgoing_server, settings = {})
|
6
|
+
super(settings)
|
7
|
+
@incoming_server, @outgoing_server = incoming_server, outgoing_server
|
8
|
+
@incoming_server.receiver(self)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Delegates delivery of email to outgoing server.
|
12
|
+
#
|
13
|
+
def deliver(tmail)
|
14
|
+
@outgoing_server.deliver(tmail)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Delegates fetching emails to incoming server.
|
18
|
+
def execute
|
19
|
+
@incoming_server.execute
|
20
|
+
end
|
21
|
+
|
22
|
+
# Delegates processing of email from incoming server to receivers on
|
23
|
+
# self.
|
24
|
+
#
|
25
|
+
def receive_email(email)
|
26
|
+
@receivers.each { |r| r.receive_email(email) }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'pop_ssl'
|
2
|
+
|
3
|
+
module MList
|
4
|
+
module EmailServer
|
5
|
+
|
6
|
+
class Pop < Base
|
7
|
+
def deliver(tmail)
|
8
|
+
raise "Mail cannot be delivered through a POP server. Please use the '#{MList::EmailServer::Default.name}' type."
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute
|
12
|
+
connect_to_email_account do |pop|
|
13
|
+
pop.mails.each { |message| receive(TMail::Mail.parse(message.pop)); message.delete }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def connect_to_email_account
|
19
|
+
pop3 = Net::POP3.new(settings[:server], settings[:port], false)
|
20
|
+
pop3.enable_ssl if settings[:ssl]
|
21
|
+
pop3.start(settings[:username], settings[:password]) do |pop|
|
22
|
+
yield pop
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'net/smtp'
|
2
|
+
|
3
|
+
module MList
|
4
|
+
module EmailServer
|
5
|
+
|
6
|
+
class Smtp < Base
|
7
|
+
def deliver(tmail)
|
8
|
+
destinations = tmail.destinations
|
9
|
+
tmail.delete_no_send_fields
|
10
|
+
smtp = Net::SMTP.new(settings[:address], settings[:port])
|
11
|
+
smtp.enable_starttls_auto if settings[:enable_starttls_auto] && smtp.respond_to?(:enable_starttls_auto)
|
12
|
+
smtp.start(settings[:domain], settings[:user_name], settings[:password],
|
13
|
+
settings[:authentication]) do |smtp|
|
14
|
+
smtp.sendmail(tmail.encoded, tmail['sender'], destinations)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def execute
|
19
|
+
raise "Mail cannot be received through an SMTP server. Please use the '#{MList::EmailServer::Default.name}' type."
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|