[SalesForce] Apex Email Trigger for a specific calendar

I'm battling my way through learning Apex, and have created an email trigger for new Events posted in our organization's calendar. I think I have it right (it's still in our sandbox so I can't see what the finished email looks like), but it's sending an alert for all events on all calendars, public and individual.

My question is – Is there a way to limit the alerts to one specific calendar? We'd ideally only like the alerts for our public calendar that has the whole team added to it.

Here is my code for reference:

trigger Event_Email on Event (before insert) 
{
    String[] toAddresses = new String[] {'name@email.com'}; 
    String[] ccAddresses = new String[] {'name@email.com'};

    User userIds = [select id from User where Alias = 'names'];
        system.debug(userIds.id);

    Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();

    EmailTemplate et=[Select id from EmailTemplate where name = :'New Event'];

    System.debug('New Event'+et);

    mail.setTemplateId('00XK0000000QoCj');
    mail.setToAddresses(toAddresses);
    mail.setCcAddresses(ccAddresses);
    mail.setReplyTo('name@email.com');
    mail.setSenderDisplayName('Calendar Alert');
    mail.setBccSender(false);
    mail.setUseSignature(false);
    Event eve = trigger.new[0];
    mail.setTargetObjectId(userIds.Id);
    mail.saveAsActivity = false;

    Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}

Best Answer

Since you cannot access the public calendar Id from Apex (that I could find) you need to go to the public calendar of interest and find it in the URL.

So for example, if you go to the public calendar of interest in the UI and look at the address bar you should look for the Id after cal_lkid. Here's an example of where you might look below:

...cal=Test+Public+Calendar&cal_lkid=023xxxxxxxxxxxx&cal_lkold=...

Here's another example of what it will look like via screenshot:

enter image description here

The 023 and the trailing 12 characters(totaling to 15 characters) will be the public calendar's Id.

Please Note

This Id will be different in each organization. So if you deploy to a higher environment (like Development to Staging), you need to update the Id value in your custom label.

Once you have that, you need to find a way to access it in Apex. You can either use a custom label or you can use a custom setting. In my approach, I used a custom label (since there is only one public calendar of interest).

When you create your custom label, all you need is a description, the value (the Id found in the URL), and a name to access it (I named mine PublicCalendarOfInterest for my example).

This is what the setup for my custom label looks like:

enter image description here

Once you create your custom label, you can access it through Apex anywhere. Here's how I would do it...

First, I'll create an Apex class to handle my trigger logic:

public with sharing class EmailOnPublicEventHandler
{
    private static Id PublicCalendarOfInterestId = (Id)Label.PublicCalendarOfInterest;

    public static void SendEmailOnPublicEventCreated(List<Event> newEvents)
    {
        if(PublicCalandarOfInterestId == null)
            throw new EmailOnPublicEventHandlerException('Public calendar Id in custom label was either not initialized or invalid!');

        List<Event> newEventsInPublicCalendar = new List<Event>();

        for(Event singleEvent : newEvents)
            if(singleEvent.OwnerId == PublicCalendarOfInterestId)
                newEventsInPublicCalendar.Add(singleEvent);

        if(newEventsInPublicCalendar.size() > 0)
        {
            List<String> toListToNotify = new List<String> { 'name@email.com' }; 
            List<String> ccListToNotify= new List<String> { 'name@email.com' };

            User userToNotify = [SELECT Id FROM User WHERE Alias = 'names' LIMIT 1];

            if(userToNotify == null)
               throw new EmailOnPublicEventHandlerException('User to nofity public group event could not be found!');

            List<Messaging.SingleEmailMessage> listOfNotificationEmails = new List<Messaging.SingleEmailMessage>();

            EmailTemplate newEventTemplate = [SELECT Id FROM EmailTemplate WHERE Name = 'New Event' LIMIT 1];

            if(newEventTemplate == null)
               throw new EmailOnPublicEventHandlerException('E-mail template used to nofity public group event could not be found!');

            for(Event newPublicCalendarEvent : newEventsInPublicCalendar)
            {
                Messaging.SingleEmailMessage notificationEmail = new Messaging.SingleEmailMessage();
                notificationEmail.setTemplateId(newEventTemplate.Id);
                notificationEmail.setToAddresses(toListToNotify);
                notificationEmail.setCcAddresses(ccListToNotify);
                notificationEmail.setReplyTo('name@email.com');
                notificationEmail.setSenderDisplayName('Calendar Alert');
                notificationEmail.setBccSender(false);
                notificationEmail.setUseSignature(false);
                //Do whatever you need to for the new public event here....
                Event newEventToNotice = newPublicCalendarEvent;
                notificationEmail.setTargetObjectId(userToNotify.Id);
                notificationEmail.saveAsActivity = false;

                listOfNotificationEmails.Add(notificationEmail);
            }

            if(listOfNotificationEmails.size() > 0)
                Messaging.sendEmail(listOfNotificationEmails);
        }
    }

    public class EmailOnPublicEventHandlerException extends Exception {}
}

As you can see, I filtered the Event list by the OwnerId being equal to the custom label (which I casted as an Id). This specifies that the given Event is for that public calendar. Once I find all the Events, regardless of how many, I build an e-mail for each (Apex Limits may apply).

I made sure to handle my events in bulk instead of operating on a single event. It is 99% of the time considered best practice and it scales better in the long run. It is considered a good habit to always develop your triggers for bulk operations, regardless of how many records you expect to operate on. Think of it as future-proofing your work.

I also added a custom exception that I can throw in case certain conditions are not met. Although it is not necessary, this is great for pinpointing any problems that may occur down the line. For example, if you deployed to a higher environment and the user you wanted to notify was not in the system. You could even provide a back-up user if you assured it was consistent in all organizations.

From there, you can hook this up to your trigger code like this:

trigger Event_Email on Event (before insert) 
{
    EmailOnPublicEventHandler.SendEmailOnPublicEventCreated(Trigger.New);
}

Testing This Code

Now I have to test it to get it to work. I actually found a simple error in my code (Single E-mail Message not being initialized), which shows why testing your code is such a good idea in the first place. Everyone makes mistakes, but simple tests like these will help you catch those mistakes. Here is the test code to do that:

@isTest
public with sharing class NewCalendarEventEmailTest
{
    static testMethod void EmailsSentWhenOnPublicCalendarEvent()
    {
        Contact newContact = new Contact(FirstName='Dominick', LastName='Medley', Phone='2284527000', Email='dominick.medley@test.com');
        INSERT newContact;
        Event testEvent = new Event(OwnerId = (Id)Label.PublicCalendarOfInterest,
            Subject = 'This is a public calendar event!', IsRecurrence = false, WhoId = newContact.Id, ActivityDate = System.Today(), DurationInMinutes = 0,
            ActivityDateTime = System.Today());


        Exception shouldNotBeThrownException = null;

        Test.StartTest();
        try
        {
           INSERT testEvent;
        }
        catch(Exception ifEventHandlerFailed){ shouldNotBeThrownException = ifEventHandlerFailed; }
        Test.StopTest();

        System.AssertEquals(true, shouldNotBeThrownException == null, 'Any exception was throw during the operation of this code');
        System.AssertEquals(1, Limits.getEmailInvocations(), 'There was an issue with the e-mails being called.');
    }
}

The testing goes with a very simple happy path scenario (a path where everything goes right). Following the Arrange-Act-Assert pattern, I first setup my data in the tests, run the actual test within Test.startTest and Test.stopTest (in our case, since it is a before trigger test, I placed in the INSERT), and lastly, asserted the results.

You notice that I have two simple assert statements to clarify:

  • No exceptions were thrown during the execution of our code
  • The e-mail message was called

Please note: Regardless of how many e-mails are sent in this test (how many events are inserted), the amount of e-mail invocations will still be one since the sendEmail method is only called once.

You should always put your Act portion of your code between startTest and stopTest blocks since it gives that code separate limits (from what I understand). I also do the try/catch portion around the code to at the very least, assert that the code won't throw an error.

Some final caveats you should know are that the Custom Label, User, and E-mail Template must be available in your org BEFORE you run your tests. Otherwise, this test will fail and no deploy for you. The other thing is that the E-mail Template must be active before running the test. Otherwise it will bomb your test too.

Related Topic