quick search:
 

Use Workflow to announce new Content to all Members

Submitted by: bobvin
Last Edited: 2004-10-14

Category: CMF

Average rating is: 4.83 out of 5 (6 ratings)

Description:
Use a Portal Workflow script to
email all registered Members,
announcing new content.

Use the "listed" property as an
opt-in/opt-out toggle, by avoiding
sending email to members whose
email address is not "listed".


Source (Text):
## Script (Python) "notify_members_published_content"
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind subpath=traverse_subpath
##parameters=state_change
##title="Announce content to listed members"
##
contentObject = state_change.object
objectOwner = contentObject.Creator()
objectType = contentObject.getTypeInfo().getId()
objectTitle = contentObject.TitleOrId()
actorId = context.portal_membership.getAuthenticatedMember().getId()
actorEmail = context.portal_membership.getMemberById(actorId).email
try:
    mailhost=getattr(context, context.portal_url.superValues('Mail Host')[0].id)
except:
    raise AttributeError, "Can't find a Mail Host object"
mailList=[]
for item in context.portal_membership.listMembers():
    if item.listed and item.email:
        mailList.append(item.email)
mSubj = 'New '+objectType+' on FCFCN.ORG: "'+objectTitle+'"'
mMsg = 'From: "'+actorId+'" <'+actorEmail+'>\n'
mMsg = mMsg+'To: "'+actorId+'" <'+actorEmail+'>\n'
mMsg = mMsg+'Subject: '+mSubj+'\n\n'
mMsg = mMsg+'The '+objectType+' titled "'+objectTitle+'"\n'
mMsg = mMsg+'is posted at:\n\n'
mMsg = mMsg+'     '+state_change.object.absolute_url()+'\n\n'
mMsg = mMsg+'Check it out!\n\n'
mMsg = mMsg+'This message is sent as a service to members\n'
mMsg = mMsg+'of the FCFCN.ORG Web Development team.\n'
mMsg = mMsg+'If you do not wish to receive such notices,\n'
mMsg = mMsg+'please visit your preferences page at:\n'
mMsg = mMsg+'     http://www.fcfcn.org/portal_form/personalize_form\n'
mMsg = mMsg+'and change your Mailing List status.'
mailhost.send(mMsg, mailList, actorEmail, mSubj)

Explanation:
The site name and address is hardcoded;
obviously you'll want to change it for
use anywhere but fcfcn.org. When I get
a chance, I'll change it to grab the
data from site variables.

To install, go to

your-plone-site/portal_workflow/manage_main

Click on the workflow you use to
manage documents. By default, it's
called "plone_workflow", I think.

Click on the "scripts" tab.

Add a new python script.
Call it "announce_content_to_members".

Fill it with the above source, suitably edited.

Save it.

Go back to the workflow.

Click on the "transitions" tab.

Add a new transtition, as follows:

Id: announce
Destination State: (remain in state)
Script (after): announce_content_to_members
Role(s): Manager

Save it.

Go back to the workflow.

Click on the "states" tab.

Click on one of the states (e.g. "published")

Check the box next to the "announce"
transition to enable announcing
content in this state.

Save it.

Repeat as applicable for other states.

Publish a new piece of content.

Click on the "State" tab.

Select the "announce" radiobutton.

Click the "Save" button.

The workflow script should email a
link to the newly-published content
to all your registered members.


Comments:

BCC instead of TO by amleczko - 2004-10-14
I'd like to send e-mails using BCC field instead of TO. Somebody knows how to do it?
 
Re: BCC instead of TO by bobvin - 2004-10-14
The script is effectively doing the same as BCC headers, already.

The mailList variable (passed as the second parameter to the mailhost.send function) controls where the messages are actually sent, just as if you had entered them into a BCC field.

The "To: " header (constructed as part of the mMsg string) controls the visible part.

In the example given, I log in as "FCFCN Webmaster" with an associated email address of "webmaster@fcfcn.org".  So the generated emails are sent with the headers:

From: "FCFCN Webmaster" <webmaster@fcfcn.org>
To: "FCFCN Webmaster" <webmaster@fcfcn.org>

but all registered members receive a copy.  This is equivalent in effect to sending with a regular Email client and filling the BCC field with a list of members' addresses.

I suppose you could edit the script to insert a bogus "To: " header (or none at all) instead of setting it from the logged-in manager's email address, but that would increase the chances of the message being bounced by an overly-restrictive mailserver.  In our particular case, it would be sufficient to trigger the SpamAssassin rules and bounce the message back to sender with a "500 - Spam" error.
 
Re: Re: BCC instead of TO by amleczko - 2004-10-14
It isn't that simple. I've included 'TO:..." part but it isn't working. It puts into TO header all members e-mails. Maybe it's the mailserver i'm using? Someone knows a good mailserver (freeware)?
 
Re: Re: Re: BCC instead of TO by bobvin - 2004-10-14
Yes, I think it must be the mailserver.

As a workaround, you could send each message individually, by putting the mailhost.send() action inside the loop.  This would be less efficient (especially for a large membership) but would guarantee that the members don't see each other's email addresses.


Doesn't work with Plone? by sollust - 2004-10-14
I hate to leave this message here on this site, but I don't see the author's email address anywhere. I installed this for use on my Plone 
site. I get a traceback from my error's html source when I attempt to use the "announce" radio button. 

This is the error: (actually, there is a lot more, but this is the real meat of it...)
 File /usr/lib/zope/lib/python/Products/PythonScripts/PythonScript.py, line
 302, in _exec
    (Object: announce_content_to_members)
    (Info: ({'script': &lt;PythonScript instance at 1148b838&gt;, 'context':
 &lt;Scripts instance at 10cdc798&gt;, 'container': &lt;Scripts instance at
 10cdc798&gt;, 'traverse_subpath': []},
 (&lt;Products.DCWorkflow.Expression.StateChangeInfo instance at
 0x11947314&gt;,), {}, None))
  File Script (Python), line 6, in announce_content_to_members
AttributeError: 'None' object has no attribute 'email'

Is there a way to fix this?

Thanks...
 
Re: Doesn't work with Plone? by sollust - 2004-10-14
Actually, I found the bug. It's in line 6:
actorId = context.portal_membership.getAuthenticatedMember().getId()
actorEmail = context.portal_membership.getMemberById(actorId).email

The first of these two lines gets the Authenticated user's ID. The next line tries to get the email property of the ID, but the ID doesn't have one (because the ID is a property, too). So, I changed it to this: 
actorEmail = context.portal_membership.getAuthenticatedMember().getProperty('email', None)

Now, I don't need actorId at all. 

Also, I realized that this is supposed to work with Plone, but something must have changed between the betas and 1.0...

Sollust


Limiting announcements to specific object types by shawn - 2004-10-14
This is a great script. Is there any way to send announcements when only specific content types are published (say, news items)? Thanks.


send announcement based on local role by billpage - 2004-10-14
# This script sends an announcement email to those users who
# are able to view the object. It can be used even if the state
# is private when other users have appropriate local roles

contentObject = state_change.object
objectOwner = contentObject.Creator()
objectType = string.lower(contentObject.getTypeInfo().getId())
objectView = '/view'
objectTitle = contentObject.TitleOrId()
actorId = context.portal_membership.getAuthenticatedMember().getId()
actorEmail = context.portal_membership.getMemberById(actorId).email
try:
    mailhost=getattr(context, context.portal_url.superValues('Mail Host')[0].id)
except:
    raise AttributeError, "Can't find a Mail Host object"
mailList=[]
for thisMember in context.portal_membership.listMembers():
    if thisMember.listed and thisMember.email:
        try:
            if not contentObject.acquiredRolesAreUsedBy( 'View' ):
                for p in contentObject.rolesOfPermission( 'View' ):
                     if p['selected']:
                         if p['name'] in thisMember.getRolesInContext(contentObject):
                             mailList.append(thisMember.email)
            else:
               mailList.append(thisMember.email)
        except:
            pass

mSubj = 'New '+objectType+': '+objectTitle
if len(mSubj)>60:
    mSubj = mSubj[:60]+'...'
mMsg = 'To: ListedMember\n\n'
#mMsg = 'From: "'+actorId+'" <'+actorEmail+'>\n'
#mMsg = mMsg+'To: "'+actorId+'" <'+actorEmail+'>\n'
#mMsg = mMsg+'Subject: '+mSubj+'\n\n'
mMsg = mMsg+'The '+objectType+'\n\n  '+objectTitle+'\n\n'
mMsg = mMsg+'is available at:\n\n'
mMsg = mMsg+state_change.object.absolute_url()+objectView+'\n\n'
mMsg = mMsg+'Please visit the portal to review it.\n\n'
mMsg = mMsg+'This item was:\n\nCreated'
for revision in state_change.getHistory():
  if revision.get('action'):
    mMsg = mMsg+revision.get('action','')
  if revision.get('actor'):
    mMsg = mMsg+' by '+revision.get('actor','')
  if revision.get('time'):
    mMsg = mMsg+' on '+container.toPortalTime(revision.get('time'))
  if revision.get('comments'):
    mMsg = mMsg+'\n\n  '+revision.get('comments','No Comment')+'\n'
  mMsg = mMsg+'\n'
mMsg = mMsg+'This message is sent as a service to registered '
mMsg = mMsg+'members of the DRDKIM Portal.\n'
mMsg = mMsg+'If you do not wish to receive such notices, '
mMsg = mMsg+'please visit your preferences page at:\n\n'
mMsg = mMsg+'http://x.y.com/portal_form/personalize_form\n\n'
mMsg = mMsg+'to change your Mailing List status to (not listed).'

# For debugging
#mMsg = mMsg+'\n\nCc:'
#for thisMember in mailList:
#    mMsg = mMsg+' '+thisMember
#mailhost.send(mMsg, [actorEmail], actorEmail, mSubj)

mailhost.send(mMsg, mailList, actorEmail, mSubj)

-- modifications based on: http://www.zopelabs.com/cookbook/1018022911