Trigger: Prevent Contact being Created/Updated with duplicate Email

Scenario:  Write Trigger on Contact to prevent inserting or updating duplicate Contacts. Use Email field to compare with existing Contacts record.

ContactTrigger
trigger ContactTrigger on Contact (before insert, before update) {
    if(trigger.isBefore && trigger.isInsert || trigger.isUpdate) { 
        ContactTriggerHandler.checkDuplicateEmail(trigger.new);
    }
}
ContactTriggerHandler Approach 1
public class ContactTriggerHandler {

    public static void checkDuplicateEmail(List<Contact> conLst) { 
        // What we have handled - allow to insert contact with null/empty email
        Set<String> setEmail = new Set<String>();
        Map<String,Id> mapExistingEmailtoCon = new Map<String,Id>();
        for(Contact con : conLst){
            if(con.Email != null || con.Email != ''){
                setEmail.add(con.Email.toLowerCase());
            }
        }
        for(Contact cont : [Select Id , Email from Contact where Email In: setEmail]){
            mapExistingEmailtoCon.put(cont.Email.toLowerCase(), cont.Id);
        }
        System.debug(mapExistingEmailtoCon);
        for(Contact con : conLst){
            if(con.Email!= null && mapExistingEmailtoCon.containsKey(con.Email.toLowerCase())){
                con.addError('Contact with same email already exist');
            }
        }
    }
}
ContactTriggerHandler Approach 2
public class ContactTriggerHandler {
    public static void checkDuplicateEmailOnInsert(List<Contact> conLst) { 
        // Approach 2 - This will hit limit
        // What we have handled - allow to insert contact with null/empty email
        // Problems:Here limit will be hit Solution
    	List<Contact> existingContacts = [Select Email From Contact];
        List<String> emailsLst = new List<String>();
        for(Contact con: existingContacts) { 
            emailsLst.add(con.Email);
        }
        for(Contact con: conLst) { 
            if(con.Email != null && con.Email != '' && emailsLst.contains(con.Email)){
                con.AddError('Contact with same email already exist');
            } 
        }
    }
}

How the code works

  1. The trigger runs before insert and before update and passes the Contacts to the handler.
  2. The handler collects the lowercase emails of the new Contacts in a Set.
  3. One query finds existing Contacts with those emails and stores them in a Map.
  4. Any Contact whose email is already in the Map gets an error through addError.

Problems in this version

  • The null check is wrong. con.Email != null || con.Email != '' is always true, so a Contact with no email causes a null pointer error on toLowerCase().
  • Updates block themselves. When you edit an existing Contact, the query finds that same Contact, so you cannot save it even when the email did not change.
  • Duplicates inside one batch are not found. Two new Contacts with the same email in one import both pass.

Improved handler

public class ContactTriggerHandler {
    public static void checkDuplicateEmail(List<Contact> conLst) {
        Set<String> emails = new Set<String>();
        for (Contact con : conLst) {
            if (String.isNotBlank(con.Email)) {
                emails.add(con.Email.toLowerCase());
            }
        }
        if (emails.isEmpty()) {
            return;
        }
        Map<String, Id> existing = new Map<String, Id>();
        for (Contact c : [SELECT Id, Email FROM Contact WHERE Email IN :emails]) {
            existing.put(c.Email.toLowerCase(), c.Id);
        }
        Set<String> seen = new Set<String>();
        for (Contact con : conLst) {
            if (String.isBlank(con.Email)) {
                continue;
            }
            String key = con.Email.toLowerCase();
            Id existingId = existing.get(key);
            if ((existingId != null && existingId != con.Id) || seen.contains(key)) {
                con.addError('Contact with same email already exists.');
            }
            seen.add(key);
        }
    }
}
trigger ContactTrigger on Contact (before insert, before update) {
    ContactTriggerHandler.checkDuplicateEmail(Trigger.new);
}

How to test

  1. Create a Contact with an email. Create a second one with the same email in capital letters. The second should fail.
  2. Edit the first Contact without changing the email. It should save.
  3. Create a Contact with no email. It should save.
  4. Import two Contacts with the same new email in one file. Both rows with the duplicate should fail.

FAQ

Could duplicate rules do this?
Yes. A matching rule on Email with a duplicate rule blocks duplicates without code and gives users a clear message.

Why lowercase the email?
It makes the comparison the same for upper and lower case letters.

Leave a Reply