Maintaining clean and reliable data in Salesforce is critical, especially when dealing with standard objects like Contacts. One of the most common data hygiene issues faced by organizations is the creation of duplicate Contact records, often due to users entering the same email address multiple times. These duplicates can clutter your database, confuse sales reps, and skew reports.
In this blog, you’ll learn how to build a robust Apex Trigger in Salesforce that automatically checks for duplicate email addresses during insert and update operations on the Contact object. If a duplicate is found, the trigger throws a custom error to the user, preventing the record from being saved. This solution is lightweight, scalable, and perfect for orgs that want more control than standard duplicate rules offer. Plus, we’ve included a modular handler method, making the trigger logic clean, reusable, and easy to maintain.
🧠 Why This Matters
Even with built-in duplicate rules and matching rules available in Salesforce, they may not always be enough—especially if your org requires custom logic, wants to avoid reliance on standard matching settings, or needs trigger-level validation for integrations or complex business rules.
This trigger solves exactly that. It programmatically checks whether the Contact’s email address already exists in the system and blocks the save operation with a user-friendly error message like “Duplicate email”. This not only improves user accountability but also protects your CRM from data clutter and downstream issues.
🔍 What You’ll Learn from This Blog
-
How to build an Apex Trigger handler method to detect duplicate emails
-
The importance of using Sets for efficient email tracking and lookup
-
How to handle both Insert and Update scenarios while avoiding false positives
-
How to write bulk-safe, governor-limit-friendly Apex logic
-
When and how to apply
addError()
on sObjects for clean user messaging -
Why separating trigger logic into a handler method improves maintainability
-
How this solution complements standard Salesforce duplicate management features
⚙️ How It Works – Technical Overview
The handler method preventDuplicateEmail
accepts a list of incoming Contacts and a map of old values (to handle updates). It uses the following flow:
-
Extracts non-null email addresses from the incoming Contact records.
-
If in update mode, it filters out unchanged emails to avoid false errors.
-
Queries Salesforce for any existing Contacts with matching emails.
-
Builds a
Set<String>
of emails already in use. -
Loops through the incoming Contact list and throws an error using
addError()
if the email already exists in the system.
This approach is completely bulkified, meaning it can safely handle batch inserts or updates (e.g., via Data Loader or API), and will still maintain optimal performance.
👨💻 Use Cases & Customization Ideas
-
Apply similar logic on Leads, Users, or custom objects with email fields.
-
Extend the logic to check across objects, e.g., prevent a Contact email from duplicating a Lead email.
-
Customize error messages based on user role, profile, or business unit.
-
Add logging or notifications for attempted duplicates for auditing purposes.
🎯 Who Should Use This Solution?
-
Salesforce Admins looking to enforce strict data integrity at the record level.
-
Developers who need custom duplicate prevention logic beyond standard duplicate rules.
-
Integration Specialists building middleware processes that insert/update Contacts programmatically.
-
Consultants helping clients with large datasets or highly sensitive CRM environments.
🎥 Visual Walkthrough & Hands-On Practice
To help you fully grasp and implement this logic, we’ve included a YouTube playlist with visual demos and step-by-step explanations. You’ll get to practice real-time in your developer org and see how each part of the code works.
Solution:
public static void preventDuplicateEmail(List<Contact>
conList,Map<Contact> oldMap){
Set<String> emailSet= new Set<String>();
for(Contact con:conList){ if(oldMap==null
&& con.Email!=null){
emailSet.add(con.Email);
}else{ if(con.Email!=null &&
con.Email!=oldMap.get(con.Id).Email){
emailSet.add(con.Email);
}
}
}
List<Contact> existingContactList= new List<Contact>([SELECT Id,Email
FROM Contact WHERE Email IN:emailSet]);
Set emailListForExisting= new
Set<String>(); if(!existingContactList.isempty()){
for(Contact con:existingContactList){
emailListForExisting.add(con.Email);
}
}
for(Contact con:conList){
if(emailListForExisting.contains(con.Email)){
con.addError(‘Duplicate email’);
}
}
}