Apex email services. The short story here is that you can send email to your Salesforce Org which is then processed by some Apex logic in order to support the specific needs of your business.
One of our clients uses an Apex email service to automate Case creation for customer support issues. We have another client that has an email service for parsing CSV attachments in order to update their Org exchange rates. Just two ideas for how real-world Salesforce customers use Apex email services for automation.
Anyway, maybe you’ve seen that Apex code sample that uses an Apex email service for handling email opt outs? Here’s a reimagined take on that logic.
/* Created by: Greg Hacic Last Update: 25 October 2019 by Greg Hacic Questions?: greg@interactiveties.com Notes: - InboundEmailHandler interface to set hasOptedOutOfEmail to TRUE for Contact and Lead records - tests located at unsubscribeTest.cls */ public class unsubscribe implements Messaging.InboundEmailHandler { //access an InboundEmail object to retrieve the contents, headers and attachments of inbound emails public Messaging.InboundEmailResult handleInboundEmail(Messaging.InboundEmail email, Messaging.InboundEnvelope envelope) { Messaging.InboundEmailResult result = new Messaging.InboundEmailResult(); //InboundEmailResult object for returning the result of the Apex Email Service if (String.isNotBlank(email.subject) && email.subject.containsIgnoreCase('unsubscribe')) { //if the subject is not null, blank or empaty '' AND the subject contains the string 'unsubscribe' (regardless of case) if (!System.isFuture() && !System.isBatch()) { //if not currently in future or batch context handleUnsubscribeFuture(envelope.fromAddress); //process the email address asynchronously } else { //otherwise handleUnsubscribe(envelope.fromAddress); //process the email address synchronously } } result.success = true; //set the InboundEmailResult.Success boolean to true return result; //return the result } //updates the hasOptedOutOfEmail value to false for a passed email address and object name public static void hasOptedOutOfEmail(String emailAddress, String objectName) { List<SObject> updatedRecords = new List<SObject>(); //list of SObjects String soql = 'SELECT Id FROM '+objectName+' WHERE Email = \''+emailAddress+'\' AND hasOptedOutOfEmail = FALSE'; //construct the query statement List<SObject> objectList = Database.query(soql); //execute the query > populate the list Schema.SObjectType t = Schema.getGlobalDescribe().get(objectName); //grab the sObject token for the object name passed to the method for (SObject r : objectList) { //for each object in the list SObject o = t.newSObject(r.Id); //construct a new sObject of this type, with the specified Id o.put('hasOptedOutOfEmail', TRUE); //set the hasOptedOutOfEmail value to TRUE updatedRecords.add(o); //add the SObject to the list } if (!updatedRecords.isEmpty()) { //if the list is not empty List<Database.SaveResult> updateResults = Database.update(updatedRecords, false); //update SObject records allow for partial fail } } //execute the handleUnsubscribe method asynchronously public static void handleUnsubscribe(String s) { hasOptedOutOfEmail(s, 'Lead'); //handle Lead Opt Outs hasOptedOutOfEmail(s, 'Contact'); //handle Contact Opt Outs } //execute the handleUnsubscribe method asynchronously @future //future annotation indicates asynchronous execution public static void handleUnsubscribeFuture(String s) { handleUnsubscribe(s); //pass the string to the handleUnsubscribe method } }
Removing the tests from the Apex class itself is a best practice. That test coverage is done in the class below:
/* Created by: Greg Hacic Last Update: 25 October 2019 by Greg Hacic Questions?: greg@interactiveties.com Notes: - tests unsubscribe.cls (95.83% coverage) */ @isTest private class unsubscribeTest { //tests unsubscribe @isTest //defines method for use during testing only static void validSubject() { //BEGIN: Some Setup Items... //insert an account List<Account> accounts = new List<Account>(); accounts.add(new Account(BillingCity = 'Denver', BillingPostalCode = '80202', BillingStreet = '201 W Colfax Ave, First Floor', BillingState = 'CO', BillingCountry = 'US', Name = 'Tess Trucking Co.', ShippingCity = 'Denver', ShippingPostalCode = '80202', ShippingStreet = '201 W Colfax Ave, First Floor', ShippingState = 'CO', ShippingCountry = 'US')); accounts.add(new Account(BillingCity = 'Buffalo', BillingPostalCode = '14202', BillingStreet = '65 Niagara Square', BillingState = 'NY', BillingCountry = 'US', Name = 'Tess Financial Corp', ShippingCity = 'Buffalo', ShippingPostalCode = '14202', ShippingStreet = '65 Niagara Square', ShippingState = 'NY', ShippingCountry = 'US')); accounts.add(new Account(BillingCity = 'San Francisco', BillingPostalCode = '94110', BillingStreet = '3180 18th St', BillingState = 'CA', BillingCountry = 'US', Name = 'Tess Health LLC')); insert accounts; //insert Contacts List<Contact> contacts = new List<Contact>(); contacts.add(new Contact(AccountId = accounts[0].Id, Email = 'tess@example.com', FirstName = 'Tess', LastName = 'Dachshund')); contacts.add(new Contact(AccountId = accounts[0].Id, Email = 'fake.name@example.com', FirstName = 'Grachus', LastName = 'Dachshund')); contacts.add(new Contact(AccountId = accounts[1].Id, Email = 'fake.name@example.com', FirstName = 'Priscus', LastName = 'Dachshund')); contacts.add(new Contact(AccountId = accounts[1].Id, Email = 'spartacus@example.com', FirstName = 'Spartacus', LastName = 'Dachshund')); contacts.add(new Contact(AccountId = accounts[2].Id, Email = 'crixus@example.com', FirstName = 'Crixus', LastName = 'Dachshund')); contacts.add(new Contact(AccountId = accounts[2].Id, Email = 'fake.name@example.com', FirstName = 'Oenomaus', LastName = 'Dachshund')); contacts.add(new Contact(AccountId = accounts[2].Id, Email = 'gannicus@example.com', FirstName = 'Gannicus', LastName = 'Dachshund')); insert contacts; //insert Leads List<Lead> leads = new List<Lead>(); for (Integer i = 0; i < 100; i++) { leads.add(new Lead(Company = 'Example Corp', Email = 'fake.name@example.com', FirstName = 'Fake', LastName = 'Person')); } insert leads; //END: Some Setup Items... Test.startTest(); //denote testing context //email Messaging.InboundEmail email = new Messaging.InboundEmail(); //construct a new inbound email object email.fromAddress = 'fake.name@example.com'; email.fromName = 'Fake Name'; email.htmlBody = '<p>I would really like to be removed from your lists.</p>'; email.plainTextBody = 'I would really like to be removed from your lists.'; email.subject = 'Remove Me From Your List Unsubscribe'; //envelope Messaging.InboundEnvelope envelope = new Messaging.InboundEnvelope(); //construct a new inbound envelope object that stores the envelope information associated with the inbound email envelope.fromAddress = 'fake.name@example.com'; unsubscribe handler = new unsubscribe(); handler.handleInboundEmail(email, envelope); Test.stopTest(); //revert from testing context //validate the logic for (Lead l : [SELECT Email, hasOptedOutOfEmail, Id FROM Lead]) { //for all leads System.assertEquals(true, l.hasOptedOutOfEmail); //hasOptedOutOfEmail = true } for (Contact c : [SELECT Email, hasOptedOutOfEmail, Id FROM Contact]) { //for all contacts if (c.Email == 'fake.name@example.com') { System.assertEquals(true, c.hasOptedOutOfEmail); //hasOptedOutOfEmail = true } else { System.assertEquals(false, c.hasOptedOutOfEmail); //hasOptedOutOfEmail = false } } } //tests unsubscribe @isTest //defines method for use during testing only static void invalidSubject() { //BEGIN: Some Setup Items... //insert an account List<Account> accounts = new List<Account>(); accounts.add(new Account(BillingCity = 'Denver', BillingPostalCode = '80202', BillingStreet = '201 W Colfax Ave', BillingState = 'CO', BillingCountry = 'US', Name = 'Tess Corp')); insert accounts; //insert Contacts List<Contact> contacts = new List<Contact>(); for (Integer i = 0; i < 100; i++) { contacts.add(new Contact(AccountId = accounts[0].Id, Email = 'fake.name@example.com', FirstName = 'Tess', LastName = 'Dachshund'+i)); } insert contacts; //insert Leads List<Lead> leads = new List<Lead>(); for (Integer i = 0; i < 100; i++) { leads.add(new Lead(Company = 'Example Corp', Email = 'fake.name@example.com', FirstName = 'Fake', LastName = 'Person')); } insert leads; //END: Some Setup Items... Test.startTest(); //denote testing context //email Messaging.InboundEmail email = new Messaging.InboundEmail(); //construct a new inbound email object email.fromAddress = 'fake.name@example.com'; email.fromName = 'Fake Name'; email.htmlBody = '<p>I would really like to be removed from your lists.</p>'; email.plainTextBody = 'I would really like to be removed from your lists.'; email.subject = 'Remove Me From Your List'; //envelope Messaging.InboundEnvelope envelope = new Messaging.InboundEnvelope(); //construct a new inbound envelope object that stores the envelope information associated with the inbound email envelope.fromAddress = 'fake.name@example.com'; unsubscribe handler = new unsubscribe(); handler.handleInboundEmail(email, envelope); Test.stopTest(); //revert from testing context //validate the logic for (Lead l : [SELECT Email, hasOptedOutOfEmail, Id FROM Lead]) { //for all leads System.assertEquals(false, l.hasOptedOutOfEmail); //hasOptedOutOfEmail = false } for (Contact c : [SELECT Email, hasOptedOutOfEmail, Id FROM Contact]) { //for all contacts System.assertEquals(false, c.hasOptedOutOfEmail); //hasOptedOutOfEmail = false } } }