Skip to content

Class Name: SendResetPasswordEmailFlowHandler

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

API Name: SendResetPasswordEmailFlowHandler Type: Service (Invocable) Test Coverage: To be determined

Business Purpose

The SendResetPasswordEmailFlowHandler class provides a Flow-invocable method for sending password reset emails to users with a custom email template. This handler: - Resets user passwords via Salesforce API - Sends password reset link emails using custom template - Supports bulk user password resets from Flows - Handles errors gracefully with detailed logging

This supports password reset workflows for internal users or Experience Cloud users.

Class Overview

  • Scope/Sharing: with sharing - Respects record-level security
  • Key Responsibilities:
  • Reset user passwords via System.resetPasswordWithEmailTemplate
  • Send emails using custom template (AANP_FORGOT_PASSWORD)
  • Track failed user resets
  • Throw exception if any users fail

Public Methods

sendResetEmails

@InvocableMethod(label='Send Password Reset Email' description='Resets user password and sends reset link email using a custom template')
public static void sendResetEmails(List<Id> userIds)

Purpose: Resets passwords and sends custom reset emails for a list of users.

Parameters: - userIds (List) - User record IDs to reset passwords for

Returns: void - Throws exception if any users fail

Business Logic:

  1. Loop Through Users (lines 7-19):
    List<Id> failedUserIds = new List<Id>();
    
    for (Id userId : userIds) {
        try {
            System.resetPasswordWithEmailTemplate(
                userId,
                true, // sendEmail
                'AANP_FORGOT_PASSWORD'
            );
        } catch (Exception ex) {
            failedUserIds.add(userId);
            System.debug('Failed to send reset email for user ' + userId + ': ' + ex.getMessage());
        }
    }
    

System.resetPasswordWithEmailTemplate Parameters: - userId: User to reset - sendEmail: true (send email immediately) - templateName: 'AANP_FORGOT_PASSWORD' (custom template developer name)

  1. Error Aggregation (lines 15-18):
  2. Catches exceptions for individual users
  3. Adds failed user IDs to list
  4. Logs error message via System.debug
  5. Continues processing other users

  6. Final Exception (lines 21-23):

    if (!failedUserIds.isEmpty()) {
        throw new AuraHandledException('Some users could not be processed. Please check logs for details.');
    }
    

  7. Throws exception if ANY user failed
  8. Generic error message (details in debug logs)
  9. Fails entire Flow transaction

Issues/Concerns: - ⚠️ All-or-Nothing: Throws exception if any user fails, rolling back successful resets - Consider returning list of failed IDs instead - ⚠️ Generic Error Message: User-facing message doesn't specify which users failed - ⚠️ Hardcoded Template: 'AANP_FORGOT_PASSWORD' template name hardcoded (line 13) - Should be parameter or custom metadata - ⚠️ No Template Validation: Doesn't verify template exists before calling - ⚠️ Debug Logging Only: Failed user details only in debug logs (lost after 24 hours) - ✅ Bulk Support: Processes multiple users in single invocation - ✅ Individual Error Handling: Continues processing after individual failures

System.resetPasswordWithEmailTemplate Behavior: - Generates new random password - Marks password as expired (forces reset on next login) - Sends email with reset link - Link expires after configurable period (default 24 hours) - Throws exception if: - User doesn't exist - User is inactive - Template doesn't exist - User has no email address

Flow Usage Example:

Flow: Bulk Password Reset

Get Records: users (User collection)
  Criteria: IsActive = true AND Profile = 'Community User'

Action: Send Password Reset Email
  userIds: {!users.Id}

Decision: Check if action succeeded
  If no fault
    Screen: "Password resets sent successfully"
  Else (Fault)
    Screen: "Some password resets failed. Check logs."

Recommended Improvements:

@InvocableMethod(label='Send Password Reset Email')
public static List<ResetResult> sendResetEmails(List<ResetRequest> requests) {
    List<ResetResult> results = new List<ResetResult>();

    for (ResetRequest req : requests) {
        for (Id userId : req.userIds) {
            ResetResult result = new ResetResult();
            result.userId = userId;

            try {
                String templateName = String.isNotBlank(req.templateName)
                    ? req.templateName
                    : 'AANP_FORGOT_PASSWORD';

                System.resetPasswordWithEmailTemplate(
                    userId,
                    true,
                    templateName
                );

                result.success = true;
                result.message = 'Reset email sent successfully';

            } catch (Exception ex) {
                result.success = false;
                result.message = ex.getMessage();
                System.debug(LoggingLevel.ERROR, 'Password reset failed for ' + userId + ': ' + ex.getMessage());

                // Log to custom object for persistent tracking
                insert new Flow_Error_Log__c(
                    FlowName__c = 'Send Password Reset Email',
                    ErrorMessage__c = ex.getMessage(),
                    ObjectName__c = 'User',
                    FlowRunDateTime__c = System.now()
                );
            }

            results.add(result);
        }
    }

    return results;
}

public class ResetRequest {
    @InvocableVariable(required=true)
    public List<Id> userIds;

    @InvocableVariable(required=false)
    public String templateName;
}

public class ResetResult {
    @InvocableVariable
    public Id userId;

    @InvocableVariable
    public Boolean success;

    @InvocableVariable
    public String message;
}

Dependencies

Salesforce Objects

  • User (Standard Object)
  • Fields: Id, Email, IsActive
  • Access: Read (for password reset)

Custom Settings/Metadata

  • EmailTemplate: AANP_FORGOT_PASSWORD
  • Type: Classic Email Template
  • Must be active and available
  • Must exist in org

Other Classes

  • None (standalone handler)

External Services

  • Salesforce Email Services: Sends password reset emails

Design Patterns

  1. Invocable Method Pattern: Flow integration
  2. Error Aggregation: Collects failures before throwing exception
  3. Try-Catch Per Item: Continues processing after individual failures
  4. All-or-Nothing: Fails transaction if any user fails (design choice)

Governor Limits Considerations

Current Impact (Per Transaction)

  • Email Sends: 1 email per user
  • Daily Email Limit: Consumes from org daily limit
  • SOQL Queries: 0 (no queries)
  • DML Statements: 0 (no DML operations)
  • System Methods: 1 resetPasswordWithEmailTemplate call per user

Limits

  • Maximum Emails: 10 emails per System.resetPasswordWithEmailTemplate call
  • Daily Org Limit: 5,000 emails/day (or higher based on licenses)
  • Bulk Limit: Process up to 200 users per Flow transaction (invocable limit)

Scalability Analysis

  • ⚠️ 10 Email Limit: resetPasswordWithEmailTemplate limited to 10 emails per call
  • If userIds.size() > 10, only first 10 processed
  • Should batch if more than 10 users
  • No SOQL/DML: Doesn't consume query or DML limits
  • ⚠️ Sequential Processing: Processes users one at a time (not bulkified)

Recommendations

  1. Batch Processing: Split into chunks of 10 users

    List<List<Id>> batches = partition(userIds, 10);
    for (List<Id> batch : batches) {
        // Process batch
    }
    

  2. Async Processing: For large user lists, use Queueable

    System.enqueueJob(new PasswordResetQueueable(userIds));
    

Error Handling

Exception Types Thrown

  • AuraHandledException: If any user fails (line 22)
  • System.Exception: From resetPasswordWithEmailTemplate

Exception Types Caught

  • Exception (line 15): Generic catch for:
  • User not found
  • User inactive
  • Template not found
  • No email address
  • Email delivery failure

Error Handling Strategy

  • Individual Try-Catch: Each user wrapped in try-catch
  • Error Aggregation: Collects failed user IDs
  • Final Exception: Throws if ANY failures occurred
  • Debug Logging: Logs individual failures

Error Handling Gaps

  1. Generic Error Message: Doesn't specify which users or why they failed
  2. No Persistent Logging: Debug logs lost after 24 hours
  3. All-or-Nothing: Rolls back successful resets on final exception
  4. No Retry Logic: Failed users not retried

Monitoring Recommendations

// Query recent password resets
SELECT Id, UserId, CreatedDate
FROM LoginHistory
WHERE LoginType = 'Password Reset'
  AND CreatedDate = TODAY
ORDER BY CreatedDate DESC

// Check for failed email deliveries
SELECT Id, ToAddress, Subject, Status, CreatedDate
FROM EmailMessage
WHERE Subject LIKE '%Password Reset%'
  AND CreatedDate = TODAY
  AND Status != 'Sent'

Security Considerations

Password Reset Security

  • Email Validation: User must have valid email address
  • Link Expiration: Reset links expire after 24 hours (configurable)
  • One-Time Use: Reset links can only be used once
  • Password Policy: New password must meet org password requirements

User Access

  • WITH SHARING: Respects record-level security
  • Limitation: User executing Flow must have access to User records being reset
  • Recommendation: Grant "Reset Passwords" permission to Flow runner

Best Practices

  1. Verify User Identity: Confirm user identity before allowing reset
  2. Limit Bulk Resets: Don't allow unrestricted bulk password resets
  3. Audit Trail: Log who initiated resets for compliance
  4. Template Security: Restrict access to email template

Test Class Requirements

@IsTest
public class SendResetPasswordEmailFlowHandlerTest {

    @TestSetup
    static void setup() {
        Profile p = [SELECT Id FROM Profile WHERE Name = 'Standard User' LIMIT 1];

        User testUser = new User(
            FirstName = 'Test',
            LastName = 'User',
            Email = 'testuser@example.com',
            Username = 'testuser' + System.now().getTime() + '@example.com',
            Alias = 'tuser',
            TimeZoneSidKey = 'America/Los_Angeles',
            LocaleSidKey = 'en_US',
            EmailEncodingKey = 'UTF-8',
            LanguageLocaleKey = 'en_US',
            ProfileId = p.Id
        );
        insert testUser;
    }

    @IsTest
    static void testSendResetEmails_Success() {
        User testUser = [SELECT Id FROM User WHERE Username LIKE 'testuser%' LIMIT 1];

        Test.startTest();
        try {
            SendResetPasswordEmailFlowHandler.sendResetEmails(new List<Id>{testUser.Id});
        } catch (AuraHandledException e) {
            Assert.fail('Should not throw exception for valid user: ' + e.getMessage());
        }
        Test.stopTest();

        // Verify email sent (check via EmailMessage or LoginHistory)
    }

    @IsTest
    static void testSendResetEmails_InvalidUser() {
        Id fakeUserId = '005000000000000'; // Non-existent user

        Test.startTest();
        try {
            SendResetPasswordEmailFlowHandler.sendResetEmails(new List<Id>{fakeUserId});
            Assert.fail('Should throw exception for invalid user');
        } catch (AuraHandledException e) {
            Assert.isTrue(e.getMessage().contains('could not be processed'), 'Should have error message');
        }
        Test.stopTest();
    }

    @IsTest
    static void testSendResetEmails_BulkUsers() {
        // Create multiple test users
        List<User> users = new List<User>();
        Profile p = [SELECT Id FROM Profile WHERE Name = 'Standard User' LIMIT 1];

        for (Integer i = 0; i < 5; i++) {
            users.add(new User(
                FirstName = 'Test' + i,
                LastName = 'User' + i,
                Email = 'testuser' + i + '@example.com',
                Username = 'testuser' + i + System.now().getTime() + '@example.com',
                Alias = 'tus' + i,
                TimeZoneSidKey = 'America/Los_Angeles',
                LocaleSidKey = 'en_US',
                EmailEncodingKey = 'UTF-8',
                LanguageLocaleKey = 'en_US',
                ProfileId = p.Id
            ));
        }
        insert users;

        List<Id> userIds = new List<Id>();
        for (User u : users) {
            userIds.add(u.Id);
        }

        Test.startTest();
        SendResetPasswordEmailFlowHandler.sendResetEmails(userIds);
        Test.stopTest();

        // Verify all users received reset emails
    }

    @IsTest
    static void testSendResetEmails_PartialFailure() {
        User validUser = [SELECT Id FROM User WHERE Username LIKE 'testuser%' LIMIT 1];
        Id invalidUserId = '005000000000000';

        Test.startTest();
        try {
            SendResetPasswordEmailFlowHandler.sendResetEmails(
                new List<Id>{validUser.Id, invalidUserId}
            );
            Assert.fail('Should throw exception when any user fails');
        } catch (AuraHandledException e) {
            Assert.isTrue(e.getMessage().contains('could not be processed'), 'Should have error message');
        }
        Test.stopTest();

        // Verify that valid user's reset was rolled back (due to exception)
    }
}

Test Data Requirements

  • User: Active user records with email addresses
  • Profile: Standard or custom profiles
  • EmailTemplate: AANP_FORGOT_PASSWORD must exist

Changes & History

Date Author Description
Unknown Original Developer Initial implementation
(Current) - Documentation added

Pre-Go-Live Concerns

CRITICAL

  • 10 Email Limit: resetPasswordWithEmailTemplate limited to 10 users per call
  • Add batching for > 10 users
  • Document limitation clearly

HIGH

  • Hardcoded Template Name: 'AANP_FORGOT_PASSWORD' hardcoded (line 13)
  • Make configurable via parameter or custom metadata
  • Validate template exists before calling
  • All-or-Nothing Failure: Throws exception, rolling back all resets
  • Consider returning success/failure list instead
  • Allow partial success

MEDIUM

  • Debug Logging Only: Failed user details only in System.debug
  • Add persistent error logging to Flow_Error_Log__c
  • Enable long-term troubleshooting
  • Generic Error Message: Doesn't specify which users or why
  • Return detailed error information per user

LOW

  • No Input Validation: Doesn't check for null/empty userIds
  • Add validation with appropriate error message

Maintenance Notes

📋 Monitoring Recommendations

  • Password Reset Volume: Track daily password reset requests
  • Failure Rate: Monitor percentage of failed resets
  • Template Availability: Verify AANP_FORGOT_PASSWORD template exists
  • Email Delivery: Monitor email delivery success rates

🔧 Future Enhancement Opportunities

  1. Configurable Template: Make template name a parameter
  2. Return Detailed Results: Return success/failure per user
  3. Batch Support: Handle > 10 users automatically
  4. Async Processing: Queue large user lists
  5. Persistent Logging: Log to custom object for auditing

⚠️ Breaking Change Risks

  • Changing to return results instead of void breaks existing Flows
  • Adding template parameter requires Flow updates
  • Removing exception throw changes error handling model
  • AANP_FORGOT_PASSWORD Email Template: Custom password reset template
  • Password Reset Flows: Flows using this handler
  • User Object: Standard User records
  • Login History: Tracks password reset events

Business Owner

Primary Contact: IT / User Management Team Technical Owner: Salesforce Development Team Last Reviewed: [Date]


Documentation Status: ✅ Complete Code Review Status: ⚠️ Requires enhancement for 10+ users Test Coverage: Test class needed Email Template Dependency: AANP_FORGOT_PASSWORD must exist