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
Returns: void - Throws exception if any users fail
Business Logic:
- 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)
- Error Aggregation (lines 15-18):
- Catches exceptions for individual users
- Adds failed user IDs to list
- Logs error message via System.debug
-
Continues processing other users
-
Final Exception (lines 21-23):
- Throws exception if ANY user failed
- Generic error message (details in debug logs)
- 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¶
- Invocable Method Pattern: Flow integration
- Error Aggregation: Collects failures before throwing exception
- Try-Catch Per Item: Continues processing after individual failures
- 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¶
-
Batch Processing: Split into chunks of 10 users
-
Async Processing: For large user lists, use Queueable
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¶
- Generic Error Message: Doesn't specify which users or why they failed
- No Persistent Logging: Debug logs lost after 24 hours
- All-or-Nothing: Rolls back successful resets on final exception
- 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¶
- Verify User Identity: Confirm user identity before allowing reset
- Limit Bulk Resets: Don't allow unrestricted bulk password resets
- Audit Trail: Log who initiated resets for compliance
- 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¶
- Configurable Template: Make template name a parameter
- Return Detailed Results: Return success/failure per user
- Batch Support: Handle > 10 users automatically
- Async Processing: Queue large user lists
- 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
🔗 Related Components¶
- 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