Skip to content

Class Name: UserTriggerHandler

Last Updated: 2025-09-10 Source Code: UserTriggerHandler.cls

API Name: UserTriggerHandler Type: Service (Trigger Handler) Test Coverage: TBD

Business Purpose

Synchronizes User email changes with related Account records (for portal users). When a User's email address is updated, this handler automatically updates the Username to match the new email and queues the related Account's PersonEmail field to be updated asynchronously. This ensures consistency between User and Account email addresses for portal users while avoiding mixed DML exceptions.

Class Overview

Scope and Sharing

  • Sharing Model: with sharing - Respects record-level security
  • Access Modifier: public
  • Interfaces Implemented: None (static helper class)

Key Responsibilities

  • Detect User email changes in after update trigger context
  • Update User.Username to match new email address
  • Queue Account.PersonEmail updates asynchronously via @future method
  • Avoid mixed DML exceptions by separating User and Account updates
  • Support bulk operations for multiple User records

Public Methods

handleAfterUpdate

public static void handleAfterUpdate(Map<Id, User> oldUsers, Map<Id, User> newUsers)

Purpose: Processes User records after update to synchronize email changes with related Account records.

Parameters: - oldUsers (Map): Map of User records before the update - newUsers (Map): Map of User records after the update

Returns: - void

Usage Example:

// Called from UserTrigger.trigger
trigger UserTrigger on User (after update) {
    UserTriggerHandler.handleAfterUpdate(Trigger.oldMap, Trigger.newMap);
}

Business Logic:

  1. Detect Email Changes (lines 6-23):
  2. Loops through updated Users
  3. Compares oldUser.Email vs updatedUser.Email
  4. If changed:

    • Prepares User record with new Username = Email
    • Collects AccountId and new Email for Account update
  5. User Update Logic (lines 25-28):

    // if (!usersToUpdate.isEmpty()) {
    //    update usersToUpdate;
    // }
    

  6. 🚨 CRITICAL: User update is commented out
  7. This means Username is NOT being updated
  8. Likely intentional to avoid recursion or permission issues
  9. Impact: User.Username and User.Email may become out of sync

  10. Queue Async Account Updates (lines 30-33):

  11. Calls updateAccountEmailsAsync() with byPassUpdate=true
  12. Uses @future to avoid mixed DML (User + Account updates)
  13. Only queues if Accounts exist (portal users)

⚠️ Issues/Concerns: - CRITICAL (lines 25-28): User.Username update is commented out - Username won't match Email - Commented Code: Should either uncomment or remove entirely for clarity - byPassUpdate=true (line 32): Always passes true, which means Account update is also bypassed (see below) - No Error Handling: No try-catch blocks - failures will bubble up to trigger - No Validation: Doesn't validate email format or check for duplicates - ✅ Bulk Pattern: Processes multiple Users efficiently - ✅ Mixed DML Avoidance: Properly uses @future for Account updates


updateAccountEmailsAsync

@future
public static void updateAccountEmailsAsync(Map<Id, String> accountsToUpdate, Boolean byPassUpdate)

Purpose: Asynchronously updates Account.PersonEmail to match User.Email, avoiding mixed DML exceptions.

Parameters: - accountsToUpdate (Map): Map of AccountId → new email address - byPassUpdate (Boolean): If true, skips the actual update (⚠️ defeats the purpose!)

Returns: - void

Annotations: - @future - Runs asynchronously in a separate context

Usage Example:

// Called from handleAfterUpdate
Map<Id, String> accountUpdates = new Map<Id, String>{
    acc1Id => 'newemail@example.com'
};
UserTriggerHandler.updateAccountEmailsAsync(accountUpdates, false);

Business Logic:

  1. Build Account Updates (lines 37-43):
    List<Account> accountsToUpdateList = new List<Account>();
    for (Id accountId : accountsToUpdate.keySet()) {
        accountsToUpdateList.add(new Account(
            Id = accountId,
            PersonEmail = accountsToUpdate.get(accountId)
        ));
    }
    
  2. Creates Account records with only Id and PersonEmail populated
  3. Efficient partial record update pattern

  4. Conditional Update (lines 45-47):

    if (!accountsToUpdateList.isEmpty() && byPassUpdate == false) {
        update accountsToUpdateList;
    }
    

  5. Only updates if byPassUpdate == false
  6. 🚨 CRITICAL ISSUE: handleAfterUpdate() always passes byPassUpdate=true (line 32)
  7. This means Accounts are never updated in normal flow

⚠️ Issues/Concerns: - 🚨 CRITICAL (line 45): byPassUpdate flag defeats the purpose of this method - Handler always passes true, so Accounts never update - Either remove the flag or fix the caller to pass false - No Error Handling: DML errors not caught or logged - No Validation: Doesn't validate email format - Mixed DML Safe: ✅ @future properly separates User/Account updates


Dependencies

Apex Classes

  • UserTriggerHandler (self-reference): Called from UserTrigger.trigger
  • No external class dependencies

Salesforce Objects

  • User (Standard Object)
  • Fields accessed: Id, Email, Username, AccountId
  • Operations: Read (in trigger), Update (commented out)
  • Account (Standard Object)
  • Fields accessed: Id, PersonEmail
  • Operations: Update (via @future)

Triggers

  • UserTrigger.trigger: Calls handleAfterUpdate() on after update event

Design Patterns

Trigger Handler Pattern

  • Static methods called from trigger
  • Separates trigger logic from trigger file
  • Allows for testing and reusability

@future Pattern

  • Asynchronous processing to avoid mixed DML
  • Separates User updates (setup object) from Account updates (non-setup object)

Bulk Processing

  • Accepts Map parameters for efficient processing
  • Collects updates in collections before DML
  • Supports multiple records in single transaction

Governor Limits Considerations

SOQL Queries: 0 (no queries in this class) DML Operations: 1 (@future Account update - though currently bypassed) CPU Time: Minimal - simple Map operations Heap Size: Low - small collections

Bulkification: ✅ Yes - processes multiple Users and Accounts in collections Async Processing: ✅ @future method for Account updates

Scalability: - ✅ Can handle up to 200 User updates per trigger invocation - ✅ @future method can handle bulk Account updates (up to 200) - ⚠️ No chunking for large volumes (assumes trigger batch size < 200)

Error Handling

Strategy: ⚠️ None - no try-catch blocks Logging: ⚠️ None - errors only in debug logs User Notifications: ❌ No user-facing error messages

Recommendations:

public static void handleAfterUpdate(Map<Id, User> oldUsers, Map<Id, User> newUsers) {
    try {
        // Existing logic
        if (!accountsToUpdate.isEmpty()) {
            updateAccountEmailsAsync(accountsToUpdate, false); // Fix: pass false
        }
    } catch (Exception e) {
        System.debug(LoggingLevel.ERROR, 'Error in UserTriggerHandler: ' + e.getMessage());
        // Consider logging to Flow_Error_Log__c
    }
}

@future
public static void updateAccountEmailsAsync(Map<Id, String> accountsToUpdate, Boolean byPassUpdate) {
    try {
        // Build updates
        if (!accountsToUpdateList.isEmpty() && !byPassUpdate) {
            Database.SaveResult[] results = Database.update(accountsToUpdateList, false);
            for (Database.SaveResult sr : results) {
                if (!sr.isSuccess()) {
                    System.debug(LoggingLevel.ERROR, 'Failed to update Account: ' + sr.getErrors());
                }
            }
        }
    } catch (Exception e) {
        System.debug(LoggingLevel.ERROR, 'Error updating Accounts: ' + e.getMessage());
    }
}

Security Considerations

Sharing Rules: ✅ Respects sharing via with sharing Field-Level Security: ⚠️ No FLS checks - assumes User has access CRUD Permissions: ⚠️ Not checked - relies on trigger execution context Input Validation: ❌ No email format validation

Security Concerns: - No validation that new email is valid format - No check for duplicate emails in org - Assumes User has update access to Account (portal users typically do)

Test Class

Test Class: UserTriggerHandlerTest.cls (likely exists) Coverage: TBD Test Scenarios to Cover: - User email change triggers Username update (currently commented out) - User email change triggers Account.PersonEmail update - Portal user email change syncs to Account - Non-portal user (no AccountId) doesn't trigger Account update - Bulk scenario: 200 Users with email changes - Negative: Invalid email format - Edge case: User with no related Account

⚠️ Pre-Go-Live Concerns

🚨 CRITICAL - Fix Before Go-Live

  1. Lines 25-28: User.Username Update Commented Out
  2. Username will not match Email after change
  3. This breaks standard Salesforce user login expectations
  4. Fix: Either uncomment the update or remove the code entirely
  5. Impact: Users may not be able to log in with new email

  6. Line 32: byPassUpdate Always True

  7. Accounts never actually update despite the async call
  8. Defeats entire purpose of the handler
  9. Fix: Change updateAccountEmailsAsync(accountsToUpdate, false);
  10. Impact: Account emails stay out of sync with User emails

HIGH - Address Soon After Go-Live

  1. No Error Handling
  2. DML failures will cause trigger to fail silently
  3. Add try-catch and logging
  4. Consider using Flow_Error_Log__c for persistent logging

  5. No Email Validation

  6. Doesn't validate email format before updating
  7. Could result in invalid email addresses
  8. Add email format validation

MEDIUM - Future Enhancement

  1. Commented Code (lines 25-28)
  2. Remove commented code or uncomment with explanation
  3. Improves code maintainability

  4. Boolean Flag Pattern (byPassUpdate)

  5. Consider removing the flag entirely
  6. Method should always update unless there's a clear reason not to

LOW - Monitor

  1. No Duplicate Check
  2. Doesn't check if new email already exists in org
  3. Could cause duplicate email issues
  4. Consider adding uniqueness validation

Maintenance Notes

Complexity: Low Recommended Review Schedule: Quarterly Key Maintainer Notes:

Critical Understanding:

  • This class is currently non-functional due to two bypassed updates:
  • User.Username update is commented out
  • Account update always bypassed via byPassUpdate=true
  • These appear intentional but should be documented with comments explaining why

Areas Needing Attention:

  • Decision Required: Should Username be updated? If not, remove the code.
  • Decision Required: Should Account email be updated? If not, remove async call.
  • If both updates should happen, uncomment/fix the bypass logic

Integration Points:

  • Called from UserTrigger.trigger (after update)
  • Calls UserTriggerHandler.updateAccountEmailsAsync() for Account sync
  • Related to UserChangeEventTriggerHandler (Change Data Capture approach)

Testing Considerations:

  • Must use System.runAs() to test portal user scenarios
  • @future methods require Test.startTest()/Test.stopTest() wrapper
  • Test both User and Account updates to verify sync

Documentation Status: ✅ Complete Code Review Status: 🚨 CRITICAL - Two bypassed updates defeat handler purpose Test Coverage: Requires verification Related Classes: UserChangeEventTriggerHandler (alternative CDC approach)