##Introduction
Dynamic sublists or dlists is a feature that was added by Systers to Mailman 2.1.10, this feature aims to provide flexibility to list subscribers which is discussed below in detail
Need for such a feature
Flexibility provided by dynamic lists to subscribers
Subscribe/Unsubcribe from new conversations
List subscribers can decide whether to be a part of new conversations or not. If the users decide to subscribe to new conversations, then they will receive all the messages of a new conversation unless they explicitly unsubscribe from it. If they otherwise decide to unsubscribe from new conversations,then they will receive only the first message of every new conversation unless they explicitly subscribe to it.
Advantages:
- List Users depending upon the conversation can anytime opt to be a part of conversation or not.
- It prevents the inbox to get unnecessarily filled up with conversations that are of no interest.
- It prevents List Users to either switch over to Digest or worst Unsubscribe from the entire list if many uninteresting conversations start hitting their mailbox at some point of time.
##Workflow
List Administrator and Dlists
###Create a list as Dlist or Non-Dlist
While creating a list the List Administrator can choose to define a list as
either a Dlist or Non-Dlist (original Mailman list). To enable this we add
a new attribute in MailingList
object as dlist
which is True
if the list
has Dlists enabled and False
if not.
All USE CASES mentioned at link
Architecture
###Message flow through mailman
Message moves from the IN queue to the default-posting-chain
, to enable Dlist
a new rule dlist
is added to this chain. The characteristics of this rule are:
-
If the mailing list is not dlist enabled, the rule misses
-
If the mailing list is dlist enabled and the address in the
To:
orX-Original-To
header has the address of the formlistname+new@domain
orlistaname+new+subject@domain
then the message is accepted and is copied to the dlist-posting-pipeline (i.e. dlist queue). -
If the mailing list is dlist-enabled and the address is not of the form specified in the above point, the message bouces back to the user.
Another important function of the dlist queue would be to calculate the thread
information to the MailingList
metadata object, ofcourse if the message is fit
for posting to the list. If not appropriate errors are raised which are
mentioned below. Calculation of thread information means to add the thread_id to
the metadata if the message belongs to a previously created thread, or to create
a new thread and add its id.
Users can subscribe/unsubscribe from the particular conversation by sending
emails to listname+threadname+(un)subscribe
.To process the email-commands for
subscribing/ubsubscribing to threads, we have two possible solutions. Either we
follow the current mechanism of processing the email-commands i.e. the
administrivia
rule checks for the -command
at the end and moves the message
to the hold queue where hold runner picks up the message and then again copies
the message to command queue. From here the command runner picks up every
message and takes the required action.
Note: In this case we may have some problem with two different delimiters i.e.
-
and +
, but on the plus side we get to reuse some of the existing code.
Or we could just do the check in the dlist rule and move the message to the hold queue and the further action can be similar to those is the previous paragraph.
After the messages are acccepted for posting from the default-posting-chain
the message is moved to dlist-posting-pipeline
which is slighly different
from the default-posting-pipeline
since it has to create different footers
with per-thread unsubscribe links.
Dlist-recipient handler calculates the list of the recipeients (replacing the
job of calculate-recipients
handler)
###(Un)Subscribing to the threads
As mentioned above, the users can (un)subscribe to the threads by sending email
commands to the address listname+thread+(un)subscribe@domain
. We could modify
administrivia
rule to check for the above mentioned form and hold the
message. Then hold
runner is modified to pick up the message and move it to
the dlist
queue where the dlist
runner takes the appropriate action. Dlist
runner calls the dlist
handler (like autorespond
handler in the command
runner)to process the message and sends the autoreplies
to the virgin
queue.
The subscription options can be changed by modifying the overrirde
table which
keeps track of users’ (un)subscription to each thread. The details are mentioned
below.
###Dlist Posting Pipeline
The handlers in this pipeline would be following.
- mime delete - tagger - dlist-recepients - avoid duplicates - cleanse - cook headers - subject-prefix- dlist-decorate
- RFC 2369
- to-archive
- to-digest
- to-unset
- after delivery
- acknowledge
- to-outgoing
####dlist-recipeints*
This is a simple handler like calculate-recipients
and calculates which users
are subscribed to the list and the thread. If it is a new thread the message is
sent to all the subscribers, but if it is continuation of a previous thread it
is sent only to the subscribers who are either subscribed to all the messages or
who are unsubscribed from the messages after first post but have explicitly
subscribed to the thread. The Override
model is used to check for explicit
subscriptions/unsubscriptions from a thread. The details about it are mentioned
below.
####dlist-decorate
Also, the footer would be different for the two set of recipeints mentiond above
and for that purpose decorate handler would be modified for the purpose. Or
again a new handler dlist-decorate
could be created to modify the messages for
dlists.
Note: The implementation of dlists in mailman2.1 by systers uses a second
pipeline for dlists. But it might be possible that we could re-use the existing
default-posting-pipeline
with a new dlist-recipeints
handler. Or we could
just modify calculate-recipients
handler to check for dlists and calculate the
recipeints like it is calculated in dlist-recipeints handler. Personally I would
like to have a seperate pipeline and handler to keep the whole process clean and
to reduce the errors caused by the checks (assuming the implementations of
theory is not always perfect), but I am open to the opinions from the mentors.
Probable Implementation
These new models would be created:
class Thread(model):
"""This is base model for threads which stores the message id of the first
post and in linked to the `Message` model using the `Message.thread_id`
attribute
"""
__tablename__ = 'thread'
thread_id = Column(Integer, primary_key=True)
thread_name = Column(Unicode)
base_message_id = Column(Integer, ForeignKey('message.id'), index=True)
messages = relationship('Message')
def create_thread(self, msg, msgdata, thread_name):
# Create new thread considerting the messsage as the first post
# to the thread
....
def new_thread(self, msg, msgdata, thread_name=None):
# Add relevant headers to create a thread and pass the params to
# create_thread() method.
....
def continue_thread(self, msg, msgdata, thread_id):
# Add an existing message to a thread.
...
class DlistsPrefernce(Enum):
# Recieve all posts in all thread and then explicitly unsubscribe to
# a particular thread
all_posts = 1
# Receive first posts from each thread and explicitly subscribe to the
# conversation
first_posts = 2
class Override(model):
""" This model overrides the default user preferences for subscription or
unsubscription from a particular thread. By default all 'First' posts are
received by all the users and if a user wishes to unsubscribe (assuming its
his/her default status "Receive all mails") `Override` will store this
change in preference and will unsubscribe the user. If current status is
"Only receive first post" and user particularly wishes to subscribe to a
thread then Override will store that prefrence and subscribe the user.
"""
__tablename__ = 'override'
member_id = Column(Integer, primary_key=True)
member = relationship('Member')
# Above could be user_id and user, but need to read more about difference
# between user and subscriber class
thread_id = Column(Integer, ForeignKey('thread.id'))
thread = relationship('Thread')
# These preferences are picked up from member's default preferences for
# this list and are updated if user subscribes/unsubscribes from a thread
preference = Column(Enum(DlistPreference))
Also, the following models needs to be modified to accomodate the changes for dynamic sub-lists. Only the additional attributes of the models are mentioned.
class Message(model):
__tablename__ = 'override'
thread_id = Column(Integer, ForeginKey('thread.id'))
thread = relationship('Thread')
subject = Column(Unicode())
class Preferences(model):
# This is global preference for a particular user, and will be the default
# for a user.
dlist_preference = Column(Enum(DlistPreference))
class Member(model):
# This is a per-list preference for a particular membership, it picks the
# default from above preferences.
dlist_preference = Column(Enum(DlistPreference))
##Errors
-
NewThreadError : When a user sends a email to the address
listname+new+threadname@domain
but a thread already exists with the samethreadname
this error is raised. Also, as a result of this error the email bounces back to the user asking him to either change thethreadname
or post tolistname+threadname@domain
if he meant to post to the existing thread. -
NonExistentError: This error is raised when a user sends an email to
listname+threadname@domain
and no suchthreadname
exists in the database. In this case too the email bounces back to the user with the appropriate reasons. -
MalformedRequestError: Apart from the above two errors, it the commands are in any other format that is not supported this error is raised and again the email bounces back to the user.
Postorius
This project would obviously follow by another small project (which I promise to finish ASAP after the GSoC, as adding it to this very project would make it impossible for me to finish it in this summer) which would involve providing admins with options to create lists with dynamic sublists enabled or disabled.
I think adding the dlist
flag (mentioned above) in the mailing_list
resource and adding th UI options would be enough to create a Dlist. And then it
would be followed by a new set of preferences in user’s preferences page which
would enable him to set his dlist preferences.
Timeline
The above projects can be divided into three major parts so as to concretely define the schedule of this project.
- Models
- Rules
- Handlers
All the changes described in the implementation part come under these categories( and ofcourse wiring them together and throughout the mailman core).
-
27th Apr - 25th May (Community Bonding Period): This would mainly involve fixing errors in the viability of this project. I will try building a proof of concept set of rules/handlers/models which would at least enable finding out errors in the above implementation at a very early stage.
-
Week 1 : Adding all the left out parts of the models and their attributes. This would involve
Thread
,Override
models and new methods inMember
,MailingList
and other required models/ -
Week 2 : Adding all the methods of the above mentioned models including
create_thread
,continue_thread
. Adding some tests for these models too. -
Week 3 : By the end of this week, all the utility functions to create and add threads would be completed. Also, the documentation and tests for the same would be done.
-
Week 4 : Send a pull request to simply group messages under threads. Even though this would not work obviously without rules to check for threadname, but a full features pull request with all tests passing should be working by now.
-
Week 5-6 : Create interfaces for rules, handlers, and dlist/command runner and add their implementations. Design decisions for creating new rules/handlers or adding to existing handlers.
-
Week 7-8 : Adding all the code for the smooth flowing of the message from IN queue to OUT queue. Basis dlist functionality should be working by the end of this week.
-
Week 9-10 : Modify
Member
to enable the dlist_preferences for a user. Make theOverride
model functional so that users can subscribe and unsubscribe. Also dlist-posting-pipeline should be working by now. -
Week 11-13 : Writing tests and documentation and solving problems from previous weeks.