Skip to content

A Ruby mailing list library designed to be integrated into other applications.

Notifications You must be signed in to change notification settings

aiwilliams/mlist

Repository files navigation

= MList

== DESCRIPTION:

An insane attempt to build a mail list server library that can be used in other
applications very easily. The first target is Ruby applications that can load
MList models for direct, embedded integration. It will later have a RESTful API
so that any language/architecture can be easily integrated. That may depend
heavily on community involvement...

== FEATURES/PROBLEMS:

If you have any experience with mailing lists, things can be understood most
quickly with this: MList is the mailing list server and database, your
application is the list manager.

MList is being used in a production environment as a Ruby gem/Rails plugin. I
decided not to use other list software primarily because a) we already had a
running, sophisticated list manager that I didn't care to synchronize with
something else, b) we have an exceptional UI design for listing messages in
threads and ultimately will have search and c) integration options for the other
software looked to be a problem in themselves.

And then there's the fame and glory that comes with building something like
this...

There is a LOT to do: segmenting, i18n, backscatter - only the Mailman developers
know what else. I have enough experience to know that rewrites are NEVER as easy
as they seem. Alas, I go boldly forward.

==== Thread Trees

A Thread answers a tree of messages where each message in the tree knows it's
parent, children, previous and next message, and whether it is the root or a leaf
in the tree. The messages are actually delegates, wrappers around the real
message instances from the thread.

This approach makes a few assumptions:

 # You want the tree because you will be displaying it # Moving through a tree
message by message should 'feel' right; next is determined by walking the tree
depth first.

It may or may not prove useful someday to have this knowledge in the form of
something like awesome_nested_set.

==== Extracting 'from' IP address from emails is not implemented

http://compnetworking.about.com/od/workingwithipaddresses/qt/ipaddressemail.htm

==== Deleting messages

Mail list servers don't typically deal with this, do they? When an email is sent,
it's sent! If you delete one, how can a reply to it be accurately injected into a
partially broken tree?

Since MList is designed to integrate with applications that want to distribute
messages, those applications may want to delete messages. I feel, at the time of
writing this, that the feature of deleting is NOT a problem that MList should
attempt to address in great detail. The models are ActiveRecord descendants, so
they can be deleted with #destroy, et. al. MList will not prevent that from
happening. This has implications, of course.

 # MList::Thread#tree will likely break if messages are gone.

In my own usage, I have opted for adding a column to my mlist_messages table,
deleted_at. The application will make decisions based on whether that column has
a value or not. I would suggest you implement something similar, always favoring
leaving the records in place (paranoid delete). Since MList::MailList#messages
(and other such has_many associations) don't know about this column, they will
always answer collections which still include those messages.

When an MList::MailList is destroyed, all it's MList::Messages and MList::Threads
will be deleted (:dependent => :delete_all). If no other MList::Messages are
referencing the MList::Email records, they will also be deleted.

==== Ensuring Delivery

The internet email system is a mess. Spam has proven to be a significant
problem which plagues both your inbox and the service providers who work to
deliver email into it. So, what must you, the user of MList do, to ensure that
your software plays nice, and your emails are delivered?

Of course, this problem goes well beyond MList - any email you send from your
application servers will suffer the consequences of a misconfigured domain.
Here's what I've learned:

 * Go to http://old.openspf.org/wizard.html and create your SPF record
   content, to be placed in a TXT record for your domain.
   - If you are using Google Apps, check this out:
     http://www.google.com/support/a/bin/answer.py?hl=en&answer=33786
 * Make sure you have an [email protected] and [email protected]
   address.
   - If you are using Google Apps, you will need to create 'groups' for those
     addresses, adding yourself as a recipient. You can read more about it
     here: http://blog.wordtothewise.com/2009/01/google-apps-wheres-my-abuse/
 * Be prepared to visit the Feedback Loop (FBL) configuration pages of many
   ISPs, like the one at AOL: http://postmaster.aol.com/fbl/
 * You'll probably want to have a way to resolve an email address (subscriber)
   to an IP address from which the subscriber subscribed. This will help your
   case should you have an ISP rejecting your mail.

There is a great article here:

  http://community-support.engineyard.com/faqs/guides/making-sure-your-email-gets-delivered

Although that is EY focused, it gives you a good idea of some of the things
you should get right.

== SYNOPSIS:

Let's say you want your web application to have a mailing list feature. Let's
also say you care about the UI, and you don't want to learn all about creating
the correct HTML structures for a mailing list. You want to have lots of power
for searching the mail, and you have your own strategy for managing the lists.
You love Ruby. You want MList.

== REQUIREMENTS:

You'll need some gems.

* hpricot
* uuid (macaddr also)
* tmail
* active_support
* active_record

== INSTALL:

- gem sources add http://gemcutter.org
- sudo gem install mlist

For now, copy the mlist_ tables from the spec/fixtures/schema.rb file and move
them to a new migration in your application. Run the migration.

Now you'll need to create the MList::Server and provide it with a list manager
and MList::EmailServer::Default instance. Something like this in your
environment.rb after the initialize block (our gem needs to have been loaded):

  Rails::Initializer.run do |config|
    # Please do specify a version, and check for the latest!
    config.gem "mlist", :version => '0.1.0'
  end

  MLIST_SERVER = MList::Server.new(
    :list_manager => MList::Manager::Database.new,
    :email_server => MList::EmailServer::Default.new(
      MList::EmailServer::Pop.new(
        :ssl      => false,
        :server   => 'pop.gmail.com',
        :port     => '995',
        :username => 'yourusername',
        :password => 'yourpassword'
      ),
      MList::EmailServer::Smtp.new(
        ActionMailer::Base.smtp_settings # probably good enough!
      )
    )
  )

Your list manager needs to implement only two methods. Check out (and use if you
like) the MList::Manager::Database for more information.

You'll need something to trigger the incoming server process. Take your pick from
http://wiki.rubyonrails.org/rails/pages/HowToRunBackgroundJobsInRails. In the
end, if you don't write your own incoming server thing and go with the POP GMail
example above, your background process will "MLIST_SERVER.email_server.execute".

Take a look at MList::EmailPost if you're building a UI. It can be posted to a
list something like this:

  class MyList # instances of these are given by your list manager
    def post(attributes)
      email = MList::EmailPost.new({
        :mailer => 'MyApplication'
      }.merge(attributes))
      raise 'You can validate these' unless email.valid?
      message = MLIST_SERVER.mail_list(self).post(email)
      # do what you will with message. it's already saved.
    end
  end

== LICENSE:

(The MIT License)

Copyright (c) 2008-2009 Adam Williams (aiwilliams)
Portions from Rails are Copyright (c) 2004-2008 David Heinemeier Hansson

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the 'Software'), to deal in the
Software without restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

About

A Ruby mailing list library designed to be integrated into other applications.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages