DEV Community

Cover image for Stop Unwanted Emails from Leaving Salesforce: Blocking Addresses in Email-to-Case
Rohan Mehta
Rohan Mehta

Posted on Originally published at way2force.com

Stop Unwanted Emails from Leaving Salesforce: Blocking Addresses in Email-to-Case

Stop Unwanted Emails from Leaving Salesforce: Blocking Addresses in Email-to-Case

Email-to-Case is one of Service Cloud's most useful channels — and one of the easiest to misfire. An agent hits reply-all on a case thread, and suddenly your response is bouncing around an internal distribution list, a vendor's legacy inbox, or an address that auto-replies and reopens the case in an infinite loop. Everyone who runs Email-to-Case has a story like this.

The usual quick fix is to hardcode blocked addresses into a validation rule or directly in Apex. It works for a week. Then a new address needs blocking, and someone has to edit code, run tests, and deploy. There's a cleaner pattern: keep the blocked list in a Custom Metadata Type and enforce it with a trigger. Admins can update the list; developers never touch the logic again.

The pattern in brief

A Custom Metadata Type (CMDT) acts as a configuration table that ships with your org's metadata but can be edited in Setup without a deployment. For this use case, one text field is enough:

  • CMDT label: Blocked Email Address
  • Developer name: Blocked_Email_Address__mdt
  • Field: Email_Address__c (Text, required)

Each record holds one address you never want agents emailing: no-reply@yourdomain.com, a decommissioned vendor inbox, the monitoring mailbox that replies to everything.

The enforcement: an Apex trigger on EmailMessage

The blocking happens in a before insert trigger on the EmailMessage object. Before any outbound email is committed, the handler loads the blocked list from CMDT and checks the To and CC fields. If either contains a restricted address, the email gets an addError and never sends.

trigger EmailMessageTrigger on EmailMessage (before insert) {
    if (Trigger.isBefore && Trigger.isInsert) {
        EmailMessageTriggerHandler.handleBeforeInsert(Trigger.new);
    }
}
Enter fullscreen mode Exit fullscreen mode
public class EmailMessageTriggerHandler {
    public static void handleBeforeInsert(List<EmailMessage> emailMessageList) {
        // Load blocked addresses from Custom Metadata
        Set<String> blockedEmails = new Set<String>();
        for (Blocked_Email_Address__mdt meta :
             [SELECT Email_Address__c FROM Blocked_Email_Address__mdt]) {
            if (meta.Email_Address__c != null) {
                blockedEmails.add(meta.Email_Address__c.toLowerCase());
            }
        }
        for (EmailMessage email : emailMessageList) {
            // Outbound emails related to a Case (prefix 500) only
            if (!email.Incoming && email.ParentId != null
                && String.valueOf(email.ParentId).startsWith('500')) {
                String toAddr = email.ToAddress != null
                    ? email.ToAddress.toLowerCase() : '';
                String ccAddr = email.CcAddress != null
                    ? email.CcAddress.toLowerCase() : '';
                for (String blocked : blockedEmails) {
                    if (toAddr.contains(blocked) || ccAddr.contains(blocked)) {
                        email.addError('Email not allowed to be sent to or CC\'d '
                            + 'to restricted address: ' + blocked);
                        break;
                    }
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

A few design notes worth stealing:

  • Only outbound emails (!email.Incoming) get checked — inbound messages still create cases normally.
  • Only case-related emails (the 500 prefix check on ParentId) are filtered, so the same pattern can't accidentally break other email automation in the org.
  • Case-insensitive matching avoids the classic No-Reply@ vs no-reply@ bypass.
  • Keep the logic in a handler class rather than the trigger body, so it slots into an existing trigger framework.

Setting it up

  1. Create the CMDT in Setup → Custom Metadata Types, with the Email_Address__c text field.
  2. Add records under Manage Records — one per address to block. This is the part your admins own going forward.
  3. Deploy the trigger + handler once. After that, blocking a new address is a five-second Setup task, not a deployment.

Why this pattern is worth adopting

It's dynamic — the list changes without code changes. It's secure — enforcement happens at the platform level before the email exists, so no flow or agent action can dodge it. And it's reusable — the same CMDT-driven approach works for any metadata-flavored rule: branding injection, routing overrides, feature flags.

If your org runs Email-to-Case, this is one of those small builds that prevents exactly one catastrophic "oops" email — and that's worth the afternoon.


Originally published at Way2Force

Top comments (0)