Skip to content

Trigger Name: UserTrigger

Last Updated: 2025-10-22 Source Code: https://github.com/AANP-IT/I2C.Salesforce.Metadata/blob/STAGING/force-app/main/default/triggers/UserTrigger.trigger

API Name: UserTrigger Object: User (Standard Salesforce Object) Pattern: Handler Pattern with Switch Statement

Business Purpose

This trigger monitors User email changes and synchronizes them to Person Account email addresses. When a User's email is updated, it automatically updates the associated Person Account's PersonEmail field via future method to avoid mixed DML errors, ensuring email consistency across User and Account objects.

Trigger Events

This trigger fires on the following events: - ☐ Before Insert - ☐ Before Update - ☐ Before Delete - ☐ After Insert - ☑️ After Update - ☐ After Delete - ☐ After Undelete

Trigger Handler

Handler Class: UserTriggerHandler.cls Pattern: Handler with Static Methods and Switch Statement Entry Point: Switch statement calls static handler method

trigger UserTrigger on User (after update) {
    switch on Trigger.operationType {
        when AFTER_UPDATE {
            UserTriggerHandler.handleAfterUpdate(Trigger.oldMap, Trigger.newMap);
        }
    }
}

Process Flow by Event

After Update

Purpose: Synchronize User email changes to Person Account email

Conditional Logic: - Fires When: User.Email field value changes - Example: If Email changes from 'old@email.com' to 'new@email.com'

Business Logic: 1. Compare old and new User records 2. Detect Email field changes 3. Collect AccountId and new Email for users with email changes 4. Call updateAccountEmailsAsync future method with Account updates 5. Future method updates Account.PersonEmail asynchronously

Methods Called: - UserTriggerHandler.handleAfterUpdate(Map<Id, User> oldUsers, Map<Id, User> newUsers) - UserTriggerHandler.updateAccountEmailsAsync(Map<Id, String> accountsToUpdate, Boolean byPassUpdate) @future

Field Updates: - Account.PersonEmail: Updated to match User.Email (via future method)

Related Records Updated: - Account records (PersonEmail field, asynchronous)


Key Business Rules

Validation Rules

  • Only processes User records with Email field changes
  • Only updates Accounts for Users with AccountId populated
  • Future method allows bypass via byPassUpdate parameter (default: true in trigger)

Field Updates

  • User.Username: Commented out (not currently updated)
  • Account.PersonEmail: Updated via future method
  • Person Account email synchronized
  • Mixed DML handled via future method

Bulkification & Governor Limits

Bulkified: Partial Max Records Handled: Limited by future method limits SOQL Queries: 0 in trigger DML Operations: 0 in trigger (DML in future method)

Bulkification Strategy

  • Trigger collects all email changes in bulk
  • Single future method call with all Account updates
  • Future method performs bulk DML on Accounts

Governor Limit Considerations

  • Future Method Limit: 50 future method calls per transaction
  • DML: All DML in future method (separate limits)
  • Mixed DML: Future method avoids mixed DML errors

Recursion Prevention

Strategy: Future method runs in separate transaction

Implementation: - Future method isolation prevents recursion - AccountTrigger updates don't re-trigger UserTrigger in same transaction - No explicit recursion flags

Scenarios: - User update -> Account update (via future) -> separate transaction - AccountTrigger can safely update Users without recursion risk

Execution Order & Dependencies

Order of Execution Impact

  • After Update: Runs after validation rules
  • Future method executes asynchronously after transaction commits

Dependent Triggers

  • AccountTrigger: May update Users, creating potential circular dependency
  • UserChangeEventTrigger: Handles same sync via CDC
  • Risk: Two triggers (UserTrigger + UserChangeEventTrigger) doing similar work

Dependent Processes

  • updateAccountEmailsAsync: Future method for Account updates
  • Person Account management
  • Email synchronization

Error Handling

Strategy: Future method with byPassUpdate parameter

User Experience: - Success: User saves, Account updated asynchronously - Validation Error: Standard Salesforce errors - Future Method Error: Account update fails silently (no user notification)

Logging: - No explicit logging in trigger or handler - Future method errors not captured

Rollback Behavior: - User update commits regardless of future method outcome - Future method failures don't rollback User changes

Dependencies

Apex Classes

  • UserTriggerHandler: Handler with static methods and future method

Salesforce Objects

  • User: Standard object (primary)
  • Account: Standard object (PersonEmail updated)

Custom Settings/Metadata

  • None visible

External Systems

  • None

Testing

Test Class: UserTriggerHandlerTest.cls (likely) Coverage: Unknown

Test Scenarios: - Single User email update - Bulk User email updates (200 records) - User with AccountId vs without - Email change vs other field changes - Future method execution - byPassUpdate scenarios

Performance Considerations

Average Execution Time: Fast (triggers future method) Max Records Processed: Limited by future method limits (50 per transaction) Async Processing: Yes - Future method for Account updates

Optimization Opportunities: - Replace future with Queueable for better monitoring - Consider Platform Events for complete decoupling - Add logging for future method outcomes - Optimize byPassUpdate parameter usage

Changes & History

  • Author: Original implementation (team maintained)
  • Pattern: Switch statement with future method for mixed DML
  • Note: Username update logic commented out (lines 26-28)

Pre-Go-Live Concerns

CRITICAL - Fix Before Go-Live

  • Duplicate Logic: UserChangeEventTrigger does same thing via CDC. Which one should be primary?
  • Future Method Limit: Only 50 future methods per transaction. Bulk user updates could fail
  • Username Update Commented: Lines 26-28 commented out but left in code
  • No Error Handling: Future method failures not handled or logged

HIGH - Address Soon After Go-Live

  • byPassUpdate Parameter: Always passed as true in trigger, but checked as false in future method
  • No Logging: No audit trail of email synchronization
  • Silent Failures: Account update errors not surfaced to users
  • Mixed DML Documentation: Needs documentation about mixed DML handling strategy

MEDIUM - Future Enhancement

  • Replace Future with Queueable: Better monitoring and error handling
  • Consolidate with CDC Trigger: Choose one approach (trigger or CDC)
  • Add Trigger Settings: Custom metadata to enable/disable
  • Error Notifications: Alert users when Account update fails

LOW - Monitor

  • Future Method Queue: Monitor future method execution times
  • Account Update Success Rate: Track failures
  • Performance: Monitor with bulk user updates

Maintenance Notes

Complexity: Medium Recommended Review Schedule: Quarterly

Key Maintainer Notes: - IMPORTANT: UserChangeEventTrigger does similar work via CDC. Potential duplication! - Username update logic commented out (was it intentional?) - byPassUpdate parameter logic seems inverted (passed as true, checked as false) - Future method isolates Account updates to avoid mixed DML - Always test with bulk user updates (200 records) - Future method has 50 call limit per transaction - AccountTrigger may create circular dependencies - test carefully

Deactivation Instructions: Add custom metadata check in trigger:

trigger UserTrigger on User (after update) {
    if (TriggerSettings__mdt.getInstance('UserTrigger')?.Disabled__c == true) {
        return;
    }
    switch on Trigger.operationType {
        when AFTER_UPDATE {
            UserTriggerHandler.handleAfterUpdate(Trigger.oldMap, Trigger.newMap);
        }
    }
}

Critical Questions for Team: 1. Should UserTrigger or UserChangeEventTrigger be the primary sync mechanism? 2. Was Username update intentionally commented out? 3. Is byPassUpdate parameter logic correct (passed true, used as false)? 4. How to handle duplicate triggers doing same work?