Event notification is an important part of most business applications. When something important happens, you sometimes need one or more users to take a specific action. Event notification lets you deliver your call to action automatically--at the appropriate time, and to the appropriate people or systems.

The requirements for different event notification use cases can vary in many ways and according to many factors, so you should carefully consider your event notification requirements during the design phase of your application.

Several things can determine requirements for your particular event notification system. For example:

Critical events typically can't go unnoticed, and your users might need multiple types of notification--for example a Web page update or alert, an email, and a text message--to learn about them. If you have less important events, you might want to report them to users once each day in an email digest.
Some events happen frequently, and you might need to aggregate them to cut down on noise, while other events happen rarely and require immediate notification.

Determinants such as event importance and frequency help you identify requirements.

How timely does your event notification delivery need to be? An event may not happen frequently, but you need to know right away when it does. It may be important, but the difference between a five minute and a one second notification is often use-case dependent.
Important events might require notification accountability (e.g., who was notified when), while less important event notifications might be able to go untracked.
Some events merely require notification, but there are many cases in which you need to guarantee that someone has handled the notification.

No matter what your requirements are, Force.com has many different features that make it easy to handle event notification, many of which require no programming. This article teaches you about event notification, contrasts the different Salesforce features you can use to implement it, and then actually shows you how to use those Salesforce features to suit your event notification use case.

Force.com Architectural Approaches for Event Notifications

When you need to notify Salesforce users about data that requires them to take action, consider the many event notification approaches, each of which has its own requirements and appropriate use cases.

The following table summarizes a few high-level design options for event notification.

# Approach Implementation Requirements
1 Dynamic Visualforce page Visualforce page auto-refreshes to display latest data. - Users always online and actively watching page

- Near real-time updates required

- Missed events tolerated

- Receipt of event notification not required

- Data set is concise

- Any number of events

2 Near real-time messages Workflow rule sends message when each event occurs.

A Chatter feed updates with specific events and sends optional emails.

- Users cannot always be online watching pages

- Near real-time event notification required

- Requirements do not tolerate missed events

- Proof of individual event notification required

- Data set is potentially large

- Relatively few events, low noise

3 Deferred reports Salesforce emails scheduled report. - Users cannot always be online watching pages

- Deferred (by hours) event notifications tolerated

- Requirements can tolerate missed events

- Receipt of individual event notification not required

- Data set is potentially large

- Potentially many events, high noise

Options 2 and 3 are declarative (non-programmatic) approaches that are relatively easy to implement with the point-and-click ease of Force.com. Option 1 requires code.

Scenario: Hot, Open Leads (HOLs)

This article uses a scenario to illustrate the various event notification approaches. In this scenario:

  • The Lead standard object contains information about sales leads.
  • One or more users require notifications about hot, open Leads--in other words, Leads that are created or updated with a Status = ‘Open - Not Contacted’ and a Rating = ‘Hot’.

Implementing the Observer Pattern with Visualforce

After carefully considering the three previous approaches for event notifications, assume that you decide that the correct approach is to implement a Visualforce page that automatically refreshes to show users hot, open Leads. This implementation approach is a common design pattern in object-oriented programming: the observer pattern. According to this reference site, the observable subject or observers can initiate updates.

Who triggers the update?
The communication between the subject and its observers is done through the notify method declared in the observer interface. … Usually, the notify method is triggered by the subject when its state is changed. But sometimes, when the updates are frequent, the consecutive changes in the subject determine many unnecessary refresh operations in the observer. In order to make this process more efficient the observer can be made responsible for starting the notify operation when it considers necessary.

If you relate the observer pattern to the example hot, open Leads Visualforce page, you can clearly define and contrast the two Salesforce approaches for setting up page updates.

  • Salespeople (observers) can refresh the page to pull the latest information about Leads (the observable subject). When the page auto-refreshes at regular intervals, this is known as polling.
  • Salesforce can push updates about Leads only when they happen, to the pages (subscribers) that salespeople use to work.


When events occur infrequently relative to the page refresh rate, polling can waste a lot of system resources (both on the client and server side) because pages constantly refresh, regardless of whether the events actually occur. In contrast, pushing updates to subscribers only when they happen is typically more efficient--this approach is sometimes referred to as long polling because observers request updates just as they would with traditional polling, but the server holds the request and responds only after something actually changes.

The following sections include example implementations for both traditional polling and long polling approaches.

Traditional Polling Visualforce Pages That Use <apex:actionPoller>

Perhaps the simplest way that a Visualforce page can poll Salesforce is to use the <apex:actionPoller> tag. Here is the source code for a working example of a hot, open Leads page that works in any DE org.

Custom List View Controller

public with sharing class leadListController {

  Transient Lead[] leads;
  String query = 'SELECT Id, Name, State, Company, Email, LastModifiedDate FROM Lead WHERE Status = \'Open - Not Contacted\' AND Rating = \'Hot\' ORDER BY LastModifiedDate DESC LIMIT 1000';

  public Lead[] getLeads() {
    leads = Database.query(query);
    return leads;

  public PageReference pollAction() {
    leads = Database.query(query);
    return null;

Visualforce Page

<apex:page showHeader="true" controller="leadListController" tabstyle="lead" sidebar="false">
      <apex:pageBlockTable id="leadList" value="{!leads}" var="l">
        <apex:column headerValue="Name">
          <apex:outputLink value="/{!l.id}">{!l.Name}</apex:outputLink>
        <apex:column value="{!l.state}"/>
        <apex:column value="{!l.company}"/>
        <apex:column value="{!l.email}"/>
      <apex:actionPoller action="{!pollAction}" reRender="leadList" interval="15"/>

As you can see from the source code, it’s very easy to build a page that polls Salesforce--with just a few lines of controller code and one Visualforce tag in the page itself, your work is done.

  • The only part of leadListController that’s necessary to support <apex:actionPoller> is the pollAction action method (lines 11-14) that returns a PageReference--everything else is vanilla code that is necessary to support a custom list view page, with or without the use of <apex:actionPoller>.
  • The only part of the page that’s necessary to support polling is the <apex:actionPoller> tag (line 12)--everything else is necessary to build a custom list view page.


Implementation Review

Even though there’s not much code to look at, you can learn about good and bad Visualforce page design from this simple implementation of a page that polls with <apex:actionPoller>.

  • The approach is very easy to implement.
  • The page re-renders only the section of the page that contains data records, minimizing network bandwidth consumption for each refresh.
  • The query in the controller includes a WHERE clause to target a subset of records in the Lead object. This structure can reduce the amount of database resources needed to execute the query.
  • The controller uses a transient variable for the leads array to minimize the amount of transient view state memory necessary to manage the page.
  • Page refreshes using this approach don’t consume any API calls that count toward your organization’s daily limit.
  • When nothing changes, the page still refreshes every 15 seconds, which is a relatively frequent--and potentially very wasteful--polling interval.
  • Every page refresh executes a potentially expensive database query and consumes the network bandwidth needed to transmit the data.
  • <apex:actionPoller> tags must appear inside <apex:form> tags, which increase the size of your page’s view state.

At a higher level, before considering the implementation of a page that polls with <apex:actionPoller>, you should first do some math to determine how much system resource consumption a page that polls can generate. For example, say that on average, 100 users have the page open, and most of them forget to close the page when they leave work at the end of the day.

That means:

100 users * 4 page refreshes/hour * 24 hours/day = 9600 page refreshes and database queries/day

Now, consider two additional factors that don't appear in this equation.

  • The picklist fields in the WHERE clause of the query do not have indexes (at least not by default). This means the query must scan all the Lead records for each query execution.
  • The Lead object has 1 million records or more. As the Lead object grows even larger, and the query executes even slower, the page refresh time slows down. Eventually, the page refresh time can eclipse the polling interval.

If all of this plays out as described above, the next number you should consider is your phone number, because both your company’s IT/network department and salesforce.com Customer Support will no doubt be calling you, requesting help in reducing the load that this one page is generating :-) From the salesforce.com perspective, Customer Support might suggest a custom two-column index on the Status and Rating fields so that query executions require less database server work. Next, Customer Support will probably ask you to increase the polling interval from 15 seconds to 5 minutes or more to further reduce system resource consumption.

The overall takeaway for Visualforce pages that use <apex:actionPoller> is this: Pages that poll can be very expensive and wasteful of shared platform resources, especially when your organization has many users, your target objects have many records, your polling interval is too frequent, and things don’t change all that frequently. In most cases, consider long polling using a push implementation powered by the Force.com Streaming API.

Long Polling Visualforce Pages That Use Streaming API

Force.com applications can very easily implement push technology with the Force.com Streaming API. The Streaming API topic page refers to many resources that you can use to start creating basic Visualforce pages that reflect Streaming API notifications. Rather than duplicate the lessons that those resources provide, this article provides you with a unique page architecture for Streaming API updates to implement an alternative hot, open Leads page that supports push updates.

To grasp the overall design of this solution, review the following figure.


Compared to other Streaming API examples you might come across, the architecture shown above does not create a PushTopic on the object that contains the data of interest (in this context, the Lead object). Instead, the design uses an intermediary custom object, Hot_Open_Lead__c, that maintains information about HOLs. Whenever a transaction inserts, updates, or deletes a HOL, a trigger on Lead creates a row in Hot_Open_Lead__c, which in turn triggers a Streaming API topic update to all subscribers. The Visualforce page, a subscriber to the topic, uses some nifty jQuery to update the table of HOLs as needed.

Sidebar: Using an Intermediary Object with Streaming API Designs

Using an intermediary object with a Streaming API implementation can help you work around API restrictions and limitations that might otherwise prevent you from meeting your design requirements. For example, you can use an intermediary object to work around Streaming API PushTopic query restrictions, such as lack of support for join and relationship queries.

When this article was originally written, the use of an intermediary object was necessary because the Streaming API would only send notifications when transactions insert or update rows--it would not send notifications when rows were deleted. And in the HOL implementation, the page must reflect the current list of HOLs, a list that does not include the Leads that transactions have deleted.

Starting with Winter '14, the Streaming API supports deletes as well--this would greatly simplify the implementation in this article. But rather than rework things, the original design remains so that you can learn how to use intermediary objects with the Streaming API.

To build an example of this design in a DE org, you can use a modified version of the tutorial for creating an example Visualforce page. Start by completing the prerequisite steps in a DE org, but rather than complete Step 1 as written, create a custom object Hot_Open_Lead__c with the following fields.

  • Name (Text)
  • Lead__c (Lookup: Lead)
  • Lead_Name__c: (Formula-Text: Lead__r.FirstName & " " & Lead__r.LastName)
  • State__c (Formula-Text: Lead__r.State)
  • Company__c (Formula-Text: Lead__r.Company)
  • Email__c (Formula-Text: Lead__r.Email)
  • Action__c (Picklist: Add, Update, Remove)

Next, create a trigger on Lead with the following code.

trigger leadTrigger on Lead (after insert, before update, before delete) {

  // array to store HOLs for bulk insertion
  Hot_Open_Lead__c[] hols = new List<Hot_Open_Lead__c>();

  // create HOLs records with appropriate Action__c (Add, Update, Remove) based
  // on changes to Lead records
  if (Trigger.isInsert) {
    for (Lead l : Trigger.new) {
      if ((l.Status == 'Open - Not Contacted') && (l.Rating == 'Hot')) {
        hols.add(new Hot_Open_Lead__c(Action__c = 'Add', Name = l.Id, Lead__c = l.Id));

  if (Trigger.isUpdate) {
    for (Integer i=0; i < Trigger.new.size(); i++){
      if (((Trigger.old[i].Status != Trigger.new[i].Status) ||
           (Trigger.old[i].Rating != Trigger.new[i].Rating)) && 
          ((Trigger.new[i].Status == 'Open - Not Contacted') &&
           (Trigger.new[i].Rating == 'Hot'))){
        hols.add(new Hot_Open_Lead__c(Action__c = 'Add', Name = Trigger.new[i].Id, Lead__c = Trigger.new[i].Id));
      else if ((Trigger.old[i].Status == Trigger.new[i].Status) &&
               (Trigger.old[i].Rating == Trigger.new[i].Rating)){
        hols.add(new Hot_Open_Lead__c(Action__c = 'Update', Name = Trigger.new[i].Id, Lead__c = Trigger.new[i].Id));
      else if (((Trigger.old[i].Status != Trigger.new[i].Status) ||
           (Trigger.old[i].Rating != Trigger.new[i].Rating)) && 
          ((Trigger.new[i].Status != 'Open - Not Contacted') ||
           (Trigger.new[i].Rating != 'Hot'))){
        hols.add(new Hot_Open_Lead__c(Action__c = 'Remove', Name = Trigger.new[i].Id, Lead__c = Trigger.new[i].Id));

  if (Trigger.isDelete){
    for (Lead l : Trigger.old) {
      if ((l.Status == 'Open - Not Contacted') && (l.Rating == 'Hot')) {
        hols.add(new Hot_Open_Lead__c(Action__c = 'Remove', Name = l.Id, Lead__c = l.Id));

  if (!hols.isEmpty()) {insert(hols);}


Comments in the code explain the logic of this simple trigger.

Rather than completing Step 2 as written, use the following code to create a PushTopic.

PushTopic pushTopic = new PushTopic();
pushTopic.Name = 'HOL';
pushTopic.Query = 'SELECT Id, Name, Action__c, Lead_Name__c, Company__c, State__c, Email__c, Lead__c FROM Hot_Open_Lead__c';
pushTopic.ApiVersion = 28.0;
pushTopic.NotifyForOperations = 'All';
pushTopic.NotifyForFields = 'Referenced';
insert pushTopic;

Continue by adding the necessary static resources as in Step 3. Additionally, download and add Underscore as a static resource named underscore. Then, rather than completing Step 4 as written, use the following markup to create a Visualforce page.

<apex:page showHeader="true" controller="leadListController" tabstyle="lead" sidebar="false">
<apex:includeScript value="{!$Resource.cometd}"/>
<apex:includeScript value="{!$Resource.jquery}"/>
<apex:includeScript value="{!$Resource.jquery_cometd}"/>
<apex:includeScript value="{!URLFOR($Resource.jquery_ui,'/jquery-ui-1.10.3/ui/jquery-ui.js')}"/>
<apex:includeScript value="{!$Resource.underscore}"/>
<script type="text/javascript">
var hotLeads = [];

function updateHotLeads(updates) {
	//Check our action
	HOL = updates.data.sobject;
	data = {'Id':HOL.Lead__c,'Name':HOL.Lead_Name__c,'State':HOL.State__c,'Email':HOL.Email__c,'Company':HOL.Company__c};
	if(HOL.Action__c.indexOf('Add') >= 0) {
	if(HOL.Action__c.indexOf('Update') >= 0) {
		for (var i = 0, l = hotLeads.length; i < l; i++) {
		    if (hotLeads[i].Id == data.Id) {
		        hotLeads[i] = data;
				console.log("Match Found");
	if(HOL.Action__c.indexOf('Remove') >= 0) {
		hotLeads = _.without(hotLeads, _.findWhere(hotLeads, {Id: data.Id}));
	template = $("#holList").html();
	$("#lead-"+data.Id).effect("highlight", {}, 3000);
	if (window.webkitNotifications.checkPermission() == 0) { 
	    noti = window.webkitNotifications.createNotification(null, 'Hot Lead Action', HOL.Action__c +': '+data.Name);

$(document).ready(function() {
	$('#show_button').click(function() {
	var template = $("#holList").html();
	leadListController.getHotLeads(function(res,mes) {
		hotLeads = res;
// Connect to the CometD endpoint
  url: window.location.protocol+'//'+window.location.hostname+'/cometd/24.0/',
  requestHeaders: { Authorization: 'OAuth {!$Api.Session_ID}'}
// Subscribe to the push topic
$.cometd.subscribe('/topic/HOL', function(message) {
  <h1>Hot Open Leads</h1><button id="show_button">Enable Notifications</button>
    <apex:pageBlock >
      <apex:outputPanel id="form">
	  <div id="leadTable" class="pbBody"></div>
      <script type="text/html" id='holList'>
		<table class="list" cellspacing='0' cellpadding='0' border='0' >
		        <thead class="rich-table-head">
		            <tr class="headerRow">
		                // repeat items 
		                <tr id="lead-<%= item.Id %>" class="dataRow">
		                    <!-- use variables -->
		                    <td><a href="/><%= item.Id %>"><%= item.Name %></a></td>
		                    <td><%= item.State %></td>
							<td><%= item.Company %></td>
							<td><%= item.Email %></td>						


Notice that the body of this Visualforce page is very similar to markup used for the example Visualforce page that supported <apex:actionPoller>, except that this page no longer requires <apex:form> and <apex:actionPoller> elements--this characteristic helps to reduce view state size. The head portion of the page loads a number of static resources to support the Streaming API and includes the JavaScript/jQuery needed to subscribe to, receive, and process messages from the HOL topic channel. See the comments in the script element for more detailed information.

Next, add a new remote action to support the new page.

  static public Lead[] getHotLeads() {
    String query = 'SELECT Id, Name, State, Company, Email, LastModifiedDate FROM Lead WHERE Status = \'Open - Not Contacted\' AND Rating = \'Hot\' ORDER BY LastModifiedDate DESC' LIMIT 1000;
	Lead[] leads = Database.query(query);
    return leads;

Note: You can replace the pollAction action if you like, as you no longer need it to support the actionPoller version of the page.

Set the organization-wide sharing defaults to:

  • Lead: Public Read/Write/Transfer This is necessary to populate the page initially.
  • Hot Open Lead: Private and disabled Grant Access Using Hierarchies.

Set the Visualforce controller to use with sharing in the class declaration.

Sidebar: Streaming API Implementation Security Configuration

Before you can go live with this Streaming API implementation, make sure that users that need to receive notifications have proper access to both the push topic and the Hot Open Leads object. Here's a quick approach to consider.

Start by cloning the profile Standard Platform User to create, say, Standard Platform User 2, specifically using the Salesforce Platform license.


Notice above that no permissions are enabled for push topics or the intermediary object, Hot Open Leads. Also notice that the custom profile does not include the permissions to access the Visualforce page. Instead, create a permission set named Hot Open Lead to specifically address the permissions necessary to view the Hot Open Lead implementation. Use the following configuration:

Visualforce Page Access

Enable the Visualforce page that is tied to the Streaming API implementation of Hot Open Leads.

Object Settings

Next, enable:

  • Hot Open Leads (tab): Available, Visible
  • Hot Open Leads (object): Read, View All
  • Push Topics: Read, Create, Edit, Delete
Test, Test, Test

As always, testing is critical to avoid surprises when you deploy. So make sure to set up a non-admin test user and assign the user the above custom profile and permission set. To test, modify Leads with an admin user, and have the Streaming API Visualforce page open with the non-admin test user and verify update notifications are properly seen.

Implementation Review

From the users’ perspective, the two Visualforce pages in this section of the article appear to function the same way, except when the page is updated. While the <apex:actionPoller>-based page refreshes every 15 seconds regardless of whether any changes occur, the Force.com Streaming API-based page updates only when changes do occur. Here are some other things that are good and not so good about this implementation.

  • The page uses Visualforce, so it’s easy to implement with very little markup.
  • The page updates only when a change happens to a hot, open Lead, helping to minimize the system resources needed to support event notification.
  • By using JavaScript/jQuery, only a portion of the page requires an update rather than a full page refresh, and the view state is minimal because a form element is not necessary.
  • Database overhead to support this design in minimal. Page updates happen with lightweight PushTopic messages instead of with additional database queries.
  • The design centralizes more complex logic related to the triggering event in an Apex trigger rather than the PushTopic query. Considering that PushTopic queries have several important limitations, this type of architecture can come in handy.
  • If you require 100-percent reliability for a long polling implementation, understand that CometD implementations are not fail-safe unless you implement more complex designs. To manage connections and messages that might drop, your design would need to check for such conditions or build in redundancies that help minimize such problems.

Implementing Workflow with Email Notifications

Now switch gears and assume that your requirements are different: Users need near real-time notifications about hot, open Leads but can’t watch a page constantly refresh, and you need some concrete tracking on who was notified when. To implement this approach, consider using a workflow rule that sends an email message to users. Here’s how to build such an example workflow rule.

Create an Email Template

Before building the workflow rule, it’s helpful to build the email template you plan to use when configuring the rule. To create a custom email template that’ll work for this example scenario, complete the following steps.

  1. From Setup, click Communication Templates | Email Templates.
  2. Click the Support: Escalated Case Notification template, then click Clone.
  3. After clearing the “Email Template Name” and “Template Unique Name” fields, enter a name such as “Lead: Hot Open Lead Notification” in the “Email Template Name” field, press TAB to auto-fill the next field, and modify the “Description” field accordingly.
  4. Click Save.

Next, click Edit and fine-tune the cloned template as follows, then click Save.

  1. Enter “Hot Open Lead Notification” as your subject.
  2. Enter the following text in the “Email Body” field.

The following lead is hot and open.

Name: {!Lead.Name} Link: {!Lead.Link}

The following screenshot shows how your completed email template should look.


Create a Workflow Rule

Once your email template is ready, you can build your workflow rule.

  1. From Setup, click Create | Workflow & Approvals | Workflow Rules.
  2. If you are not familiar with workflow rules, read the brief introduction on the Understanding Workflow page, and then click Continue.
  3. Click New Rule.
  4. Select the Lead object and click Next.
  5. For the rule name, enter “Hot Open Lead Notification.”
  6. For evaluation criteria, select “created, and every time it’s edited.”
  7. For rule criteria, set “Lead: Lead Status = Open … AND Lead Rating = Hot” (See the following screen.)
  8. Click Save & Next.


Next, add an action for the new workflow rule.

  1. Click Add Workflow Action and select “New Email Alert.”
  2. Enter a description and a unique name, and select the email template you just created.
  3. For testing, add yourself to the Selected Recipients list. In a real-world scenario, you would probably create a group of users and send the email alert to that group.
  4. Click Save after confirming that your screen is similar to the following one.
  5. Click Done.


New workflow rules are not active by default. Therefore, click Activate for the new rule. Feel free to test-drive your new workflow rule by adding and updating Leads so that they are hot and open. You should get email notifications accordingly.


Note: From Setup, click Logs | Email Log Files, and then click Request an Email Log to track emails sent on behalf of the workflow rule.

Implementation Review

Of course, you should be aware of both the good and the not-so-good points about the workflow rule and email alert implementation.

  • There is no coding necessary.
  • Because workflow rules fire as data changes, email notifications happen in near real time.
  • As illustrated, email alerts sent by workflow rules offer a degree of accountability, considering that you can consult logs to see which emails were sent to which users.
  • When there are many events, workflow rules that send email notifications can get noisy.

Implementing a Scheduled Report

We have one final implementation of hot, open Leads event notification: an implementation of a scheduled report. For this implementation, assume that your requirements allow for significant lag time between the event and the notification.

Here’s how you might build it once you log into your organization.

  1. From the Reports tab, click New Report.
  2. Select “Leads” as your report type, and then click Create.
  3. From the Show drop-down list, select “All Leads.”
  4. From the Date Field drop-down list, select “Last Modified.”
  5. From the Range drop-down list, select “Last 120 Days.”
  6. Next to Filters, click Add, and then add the following filters:Lead Status equals "Open - Not Contacted" AND Rating equals "Hot"
  7. Drag report columns so that the report has the First Name, Last Name, Company, Lead Status, Rating, and Email columns.
  8. Click Save and name the report. If you want to make it available to everyone, set Report Folder to Unfiled Public Reports.
  9. Click Run Report.

You should see a report similar to what appears in the the following screenshot, assuming you have Leads that are both hot and open.


Once you are satisfied with your report, click Schedule Future Runs... from the Run Report drop-down list. Notice the schedule options you have for your report.


You are limited to three options for the “Preferred Start Time,” all of which are generally during off-peak hours. When the report runs, you’ll get an HTML-formatted email that appears very similar to the report you see on the screen.

Implementation Review

Now consider some pros and cons of the scheduled report approach for event notification.

  • There is no coding necessary.
  • When there are many events, scheduled reports effectively filter out noise because they are sent only once daily, weekly, or monthly.
  • Scheduled reports offer a degree of accountability, considering that you can consult logs to see that reports were run and emails were received by users.
  • There’s a lot of lag time between notifications. With noisy environments, users will not see many events that come and go between report runs.


Force.com supports many different approaches for handling event notification, many of which require no programming. Carefully consider each option--including Visualforce pages that automatically refresh when data changes, workflow rules that send messages when events happen, or scheduled reports--then use the option that best fits your specific application requirements.

Related Resources

About the Author

Steve Bobrowski is an Architect Evangelist within the Technical Enablement team of the salesforce.com Customer-Centric Engineering group. The team’s mission is to help customers understand how to implement technically sound Salesforce solutions. Check out all of the resources that this team maintains on the Architect Core Resources page of Developer Force.

Special thanks to Josh Birk for helping me sanity-check the Visualforce page examples.