Event Subscriptions and Notifications

Revision: $Id$


1   Typical Use Cases

  1. Notify the creator of a document that it is published.
  2. Notify the submitter of a document that it is published.
  3. Notify the section reviewer that a document is submitted.
  4. Notify the forum reviewer that a post is pending approval.
  5. Notify all the intermediate workflow validators of a document that it has reached "approved" status.
  6. Notify all having "ResponsableCrise" roles that a subproblem has reached "resolved" status.
  7. Notify workspace members that a comment has been posted into a container forum.
  8. Notify the poster of a message that someone gave an answer.
  9. Notify workspace members that a document has been copy/pasted into the workspace.
  10. Notify workspace members that a new document version has been created (which means that a revision has been frozen). [Misill]
  11. When certain workflow transitions are done, it must be possible for the user to specify a flag "notify_local_only" which will instruct the user resolution mechanism to lookup only locally and not infer merged local roles. Another flag "notify_no_local" will instruct to not send any notification based on the placeful resolution. [Misill]

2   Use case analysis

The main use case is to "do something when something happens".

2.1   When?

  • Workflow transition (create, edit, submit, publish, accept, copy/cut/paste, )
  • Forum related events (New post, Reply to post, New comment)
  • Other

2.2   Do what?

  • Send emails
  • Launch some script

2.3   Sending an email

Notify whom?

  • Global list of users/groups

  • Local roles in the context of the event

    • Direct local roles on the object

      ex: the Owner role to notify the creator that his document is published.

      ex: the Reader role to notify local readers assigned to the document. (?)

    • Direct local roles on a parent of the object with specific


      Example: The SectionManagers of the Forum above the current post.

    • Merged local roles

      Example: The SectionReviewers are all potential validators.

    For publishing (and others), there are two contexts: source and destination ...

  • Local explicit list

    This is what we have currently with "MailingList".

  • Configuration list in the parent of the object giving instructions

    Example: Notification of the poster of a message that someone responded to him. A configuration object at the forum level (or above) specifies that we lookup the Owner of the Post/Thread above the new post and notify him.

  • Users having been implied by the workflow

    This is important be able to notify actors of some workflow transition. The workflow currently records actors in the history, so it is possible to extract them.

    Configuration examples:

    • those that did the transitions "edit"
    • those that did the LAST transition "edit"
    • those that did the transitions "validate" or "publish"
    • those that did any transition except "comment" and "view"

    In some workflows, we accumulate a stack of people that will be able to do some action later. We also want to be able to notify those. XXX this depends on how we store them.

    Note that there are two workflow history, the one local to the proxy and the one global to the document ID. Both may be useful depending on the context:

    • local: ex: people who have been acting on the proxy in a

      particular workspace

    • global: Example: (?)

Note that for publishing events, may want to check the source context (to notify people from the workspace) or the destination (to notify people from the section).

Send what message?

  • message defined globally. i18n.
  • message defined locally.

The message's content may have to be computed according to the context.

3   Specification

3.1   Concepts

Subscriptions Tool

portal_subcriptions is the central tool with the necessary methods to query the subscriptions and execute them.

The subscriptions are looked up locally in a .cps_subscriptions folder in the object.


A Subscription object holds the information about what kind of events are treated, the parameters needed to find the final recipients (recipients rules), and the notifications sent to the recipients.

The recipients rules are stored as sub-objects of the Subscription.


They describe how to get some recipients. A final recipient for a subscription is either a member or an explicit email. Explicit emails only make sense for email subscriptions.

If configured so, members or anonymous users can add or remove themselves from the subscription list.

The something that is actually done. Usually it involves Recipients (sending email) but that's not mandatory (triggering an arbitrary script for instance).

3.2   Scenario

An event is sent. The event has a context object, and additional properties like the user flags specified in doActionFor for a workflow transition, or the workflow transition parameters like the source/destination container.

The event is passed to the portal_subcriptions tool.

The subscriptions tool does lookups from the context and up to find the subscriptions. It then sends the notification to all applicable subscriptions.

Each subscription computes a set of recipients according to its recipient rules, and does its specific notification on the recipients.

3.3   Implementation

3.3.1   Subscriptions Tool

This tool has id 'portal_subscriptions'.


  • notify_event:

    def notify_event(self, event_type, object, infos):
    """Standard event hook.
    Get the applicable subscriptions. Sends the event to the
    For workflow events, infos must contain the additional
    keyword arguments passed to the transition.
  • getSubscriptionsFor:

    def getSubscriptionsFor(self, event_type, object, infos):
    """Get the subscriptions applicable for this event.
    Some of the parameters may be None to get all subscriptions.

Also needed API

  • To what local subscription lists can the user subscribe?

3.3.2   Subscription


  • isInterestedInEvent:

    def isInterestedInEvent(self, event_type, object, infos):
    """Is the subscription interested in the given event."""
  • sendEvent:

    def sendEvent(self, event_type, object, infos):
    """Send an event to the subscription."""
  • getRecipientsRules:

    def getRecipientsRules(self, ...):
    """Get the recipients rules objects...
    XXX matching what ?
  • setters/getters for the properties.

A Subscription instance has the following properties:

  • Event filtering:

    • filter_event_types -- The event types on which to react.

      Examples: workflow_create, workflow_in_publish

    • filter_object_types -- The types of the objects concerned by the subscription. The subscription is valid only if the context object's portal_type is in object_types.

  • Recipient filtering:

    • recipient_emails_black_list -- The emails the subscription is blocking.
  • Notifications:

    • notification_types -- What kinds of action are taken for the recipients:
      • 'tales': Call notification_expression.
      • 'email': Send an email to each recipient, according to notification_email_expression.
    • notification_expression -- A TALES expression called for each recipient. In the expression, the same namespace as in ComputedRecipient is available, with in addition:
      • email: The recipient's email.
      • member: The member if the recipient is a member, or None.
    • notification_email_expression -- A TALES expression returning the email headers and body as a string. No mail is sent if it returns a false value. In the expression, the same namespace as in action_expression is available.

3.3.3   Recipients

There are different ways of getting recipients. The following classes subclass RecipientsRule and implement different strategies. Their properties are described below.


  • getRecipients:

    def getRecipients(self, event_type, object, infos):
    """Get the recipients.
    Returns a mapping with 'members' and 'emails' as keys.


expression -- A TALES expression returning a mapping with recipients. In the expression, the following namespace is available:

  • portal: The portal object.
  • context: The context object (proxy) where the triggering event occurred.
  • proxy: Alias for context.
  • doc: context.getContent().
  • container: The context's container.
  • ancestor: If 'ancestor_local' or 'ancestor_merged' was used for recipient_roles_origins, that object, else None.
  • event_type: The triggering event type.
  • triggering_user: The user who triggered the original action.
  • DateTime: A DateTime constructor.


  • members -- The ids of the members subscribed manually.
  • members_allow_add -- Whether members are allowed to subscribe / unsubscribe manually to the Subscription (adding themselves to recipient_members).
  • groups -- The ids of the groups subscribed manually.
  • emails -- The emails subscribed manually.
  • emails_allow_add -- Whether anonymous visitors are allowed to subscribe / unsubscribe manually to the Subscription (adding themselves to recipient_emails).
  • emails_confirm -- Whether the emails have to be confirmed before being used. XXX this may be better as a global flag of portal_subcriptions instead..
  • emails_pending_add -- The emails subscribed manually but not yet confirmed.
  • emails_pending_delete -- The emails pending deletion from recipient_emails but not yet confirmed.


  • roles -- The roles subscribed.
  • origins -- A sequence describing how roles are looked up. It can contain the following keys:
    • 'local': Direct local roles from the context object are used.
    • 'merged': All merged local roles of the context object are used.
    • 'ancestor_local': Direct local roles found on an ancestor object of type in ancestor_object_types. Only the closest matching ancestor object is used.
    • 'ancestor_merged': Idem but with merged local roles.
  • ancestor_object_types -- The portal types of the ancestor where a lookup of local roles is done if origins contains 'ancestor_local' or 'ancestor_merged'.


  • origins -- A sequence of keys used to determine which object's workflow is queried for recipients. It can contain the following keys:
    • 'context': The context object.
    • 'container': The container.
    • 'ancestor': Direct local roles found on an ancestor object of type in ancestor_object_types. Only the closest matching ancestor is used.
  • ancestor_object_types -- The portal types of the ancestor where workflow is queried for recipients.
  • history_lookup -- Determines what part of the history we lookup for the transition of type matching 'transitions':
    • 'last': The last transition of matching type is used.
    • 'all': All transitions of matching type are used.
  • transitions -- The name of the transitions examined for variables. '*' means all transitions. If the first element of the list is '-', then all types except the ones following it are used.
  • variables -- The workflow variables containing used recipients (string or list). (Will often be ['actor'].)

4   Open problems