0

We are encountering a issue.

  • From InstallHandler Apex class we are invoking a Apex batch class.
  • The start and finish function have email methods.
  • When we are executing the apex batch class from developer console the emails and going out fine along with the processing of records.
  • When we are upgrading the package in org, processing of records are working fine but no emails are generated from Apex batch.

FYI - We are using managed package.

Is this a salesforce bug, that email cannot be sent when a apex batch is invoked from install handler class.

I have attached code of the install handler and the apex batch job.

Apex Batch class.

global without sharing class Account_CreatePrimaryRatioBatchable implements Database.Batchable, Database.Stateful {

public static final String RECORD_TYPE_NAME_ORGANIZATION = 'Organization';
public static final Schema.DescribeSObjectResult DESCRIBE_RESULT = Account.SobjectType.getDescribe();
public static final Map<String, Schema.RecordTypeInfo> RT_ACCOUNTS = DESCRIBE_RESULT.getRecordTypeInfosByName();
public String RECORDTYPE_ID_ORGANIZATION;
public static final string SUBJECT_PROCESS_STARTED = 'NGO Connect - Matching Gift Upgrade Processing.'; 
public static final string MESSAGE_PROCESS_STARTED = 'Upgrading to the NGO Connect or Advancement Connect Spring Release automatically upgrades Matching Gift functionality to support multiple matching gift ratios as noted in the Release Notes and Release Documentation. You will be notified when the process completes.';
public static final string SUBJECT_PROCESS_FINISHED = 'NGO Connect - Matching Gift Upgrade Complete.'; 
public static final string MESSAGE_PROCESS_FINISHED = 'Matching Gift functionality upgrade is complete. Please follow documentation to understand and use the new functionality.';
global Database.QueryLocator start(Database.BatchableContext batchableContext) {
    if(RT_ACCOUNTS.containsKey('Organization')) {
        RECORDTYPE_ID_ORGANIZATION = RT_ACCOUNTS.get('Organization').getRecordTypeId();
    }
    String query = '';
    query += 'SELECT Id';
    query += '     , Minimum_Matched__c';
    query += '     , Maximum_Matched__c';
    query += '     , Match_Ratio__c';
    query += '     , Items_Allowed__c';
    query += '     , Installments_Allowed__c';
    query += '     , Distribution_Frequency__c';
    query += '  FROM Account';
    query += ' WHERE Match_Ratio__c != NULL';

    if (RECORDTYPE_ID_ORGANIZATION != null && RECORDTYPE_ID_ORGANIZATION != '') {
        //query += '   AND RecordTypeId = :RECORD_TYPE_NAME_ORGANIZATION';
        query += '   AND RecordTypeId = :RECORDTYPE_ID_ORGANIZATION';
    }
    query += '   LIMIT 50000 ';

    sendEmail(SUBJECT_PROCESS_STARTED, MESSAGE_PROCESS_STARTED);

    // Done
    return Database.getQueryLocator(query);
}

global void execute(Database.BatchableContext batchableContext, Account[] accountList) {

    Matching_Ratio__c[] matchingRatioList = new Matching_Ratio__c[]{};
    Matching_Ratio__c[] existingMatchingRatios = [
        SELECT Id
             , Account__c
             , Is_Primary__c
          FROM Matching_Ratio__c
         WHERE Account__c IN :accountList
           AND Matching_Ratio_Type__c = 'Standard'
    ];

    set<Id> accountExistingMatchingRatioSet = new Set<Id>();
    for (Matching_Ratio__c matchingRatio : existingMatchingRatios) {
        accountExistingMatchingRatioSet.add(matchingRatio.Account__c);
    }
    for(Account account :accountList) {
        if (accountExistingMatchingRatioSet.contains(account.id) == true) {
            continue;
        }
        Matching_Ratio__c matchingRatio = new Matching_Ratio__c();
        matchingRatio.Account__c = account.Id;
        matchingRatio.Distribution_Frequency__c = account.Distribution_Frequency__c;
        matchingRatio.Installments_Allowed__c = account.Installments_Allowed__c;
        matchingRatio.Items_Allowed__c = account.Items_Allowed__c;
        matchingRatio.Matching_Ratio_Type__c = 'Standard';
        matchingRatio.Match_Ratio__c = account.Match_Ratio__c;
        matchingRatio.Maximum_Matched__c = account.Maximum_Matched__c;
        matchingRatio.Minimum_Matched__c = account.Minimum_Matched__c;
        matchingRatio.Is_Primary__c = true;
        matchingRatioList.add(matchingRatio);
    }
    insert matchingRatioList;

}

global void finish(Database.BatchableContext batchableContext) {

    sendEmail(SUBJECT_PROCESS_FINISHED, MESSAGE_PROCESS_FINISHED);
}

public static Boolean isSandbox() {
    return URL.getSalesforceBaseUrl().getHost().left(2).equalsignorecase('cs');
} 

public String sandboxEmailToRealEmail(String email) {
    if((isSandbox() || Test.isRunningTest()) && email.contains('=')) {
      email = email.substring(0, email.indexOf('@'));
      email = email.replace('=', '@');
    }
    return email;
}

public void sendEmail(String subject, String message) {
    try {
        String userEmail = [Select Id, Email from User where Id = :UserInfo.getUserId()].Email;
        userEmail = sandboxEmailToRealEmail(userEmail);
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        mail.setUseSignature(false);
        mail.setReplyTo('[email protected]');
        mail.setSenderDisplayName('xxxx');
        mail.setSubject(subject);
        String bodyString = message;
        bodyString += '\n\n Organization: '+ UserInfo.getOrganizationName() +' ('+ UserInfo.getOrganizationId() +')' ;
        bodyString += '\n User: '+ UserInfo.getName() + ' (' +UserInfo.getUserId()  + ')' ;
        bodyString += '\n Package: rC Giving ';
        mail.setPlainTextBody(bodyString);
        mail.setToAddresses(new String[]{userEmail});
        Messaging.sendEmail(new Messaging.SingleEmailMessage[]{mail});
    } catch(Exception e2) {

    }
}

}

Install Handler class

global class rC_GivingInstallHandler implements System.InstallHandler {

public System.InstallContext localContext {private get; set; }
public Version localVersion {private get; set;}
public String EmailSubject { private get; set; }
public String EmailMessage { private get; set; }
public static final String EMAIL_SUBJECT_UPGRADE_ERROR = 'NGO Connect - Matching Gift Upgrade Failed.';
public static final String EMAIL_MESSAGE_UPGRADE_OLDVERSION_ERROR = 'The Upgrade operation to the new Matching Gift model failed due to older version of the rC_Giving package. Matching Gift model needs to be upgraded manually. Please refer Release Notes for next steps.';
public static final String EMAIL_MESSAGE_UPGRADE_SCRIPT_ERROR ='The Upgrade operation to the new Matching Gift model in rC_Giving package failed. Please consult respective Release Notes for next steps.';
public static final String EMAIL_MESSAGE_ON_ERROR = 'Your installation was successful, but we detected a slight problem. To fix the problem, simply create a new Default Organization Level Value record for each rC Giving custom setting under Setup > Develop > Custom Settings.';

global void onInstall(System.InstallContext context) {
    try {
          //universal setting of custom settings, for future
          //additions of CS's, it will update or insert only
          // CS's for orgs and not for any profile or user.
        initializeOrgDefaults(context);
        localContext = context;
        localVersion = context.previousVersion();
        if(context.isUpgrade()){
            //migrateMatchingRatioData(localVersion);  RCSBIRD-13616
        }else if(context.isPush()) {
          //will be needed during push upgrade.
        }


    } catch(Exception e) {
        EmailSubject = EMAIL_SUBJECT_UPGRADE_ERROR;
        EmailMessage = EMAIL_MESSAGE_ON_ERROR;
        sendErrorEmail(e, context, EmailSubject, EmailMessage);
    }
}

/* RCSBIRD-13616
public void migrateMatchingRatioData(Version previousVersion){
    if(previousVersion.compareTo(new Version(2,31)) <= 0 && previousVersion.compareTo(new Version(2,22)) >= 0) {
        runUpgradeScript();
    } else if (previousVersion.compareTo(new Version(2,22)) < 0){
        if(matchingRatioRecordExist() == true){
            EmailSubject = EMAIL_SUBJECT_UPGRADE_ERROR;
            EmailMessage = EMAIL_MESSAGE_UPGRADE_OLDVERSION_ERROR;
            sendErrorEmail(null, localContext,EmailSubject,EmailMessage);
        }
    }
}

public boolean matchingRatioRecordExist(){
        List<Account> accountWithMatchingRatio = 
                            new List<Account>([ SELECT Id, Name
                                  FROM Account
                                  WHERE Match_Ratio__c != null
                                  LIMIT 1
                                ]);
        if(accountWithMatchingRatio.size() > 0){
            return true;
        }else{
            return false;
        }
}
*/

global void runNewOrgScript(System.InstallContext context) {
    initializeOrgDefaults(context);
}

public void initializeOrgDefaults(System.InstallContext context) {
    // Setup org defaults
    upsert Account_Setting__c.getOrgDefaults();
    upsert Account_Validation__c.getOrgDefaults();
    upsert Contact_Setting__c.getOrgDefaults();
    upsert Deliverable_Validation__c.getOrgDefaults();
    upsert Campaign_Setting__c.getOrgDefaults();
    upsert Giving_Level_Item_Setting__c.getOrgDefaults();
    upsert Giving_Level_Setting__c.getOrgDefaults();
    upsert Giving_Level_Validation__c.getOrgDefaults();
    upsert Opportunity_Allocation_Setting__c.getOrgDefaults();
    upsert Opportunity_Allocation_Validation__c.getOrgDefaults();
    upsert Opportunity_Credit_Setting__c.getOrgDefaults();
    upsert Opportunity_Credit_Validation__c.getOrgDefaults();
    upsert Opportunity_Product_Setting__c.getOrgDefaults();
    upsert Opportunity_Product_Validation__c.getOrgDefaults();
    upsert Opportunity_Setting__c.getOrgDefaults();
    upsert Opportunity_Validation__c.getOrgDefaults();
    upsert Payment_Method_Setting__c.getOrgDefaults();
    upsert Payment_Method_Validation__c.getOrgDefaults();
    upsert Planned_Giving_Setting__c.getOrgDefaults();
    upsert Planned_Giving_Validation__c.getOrgDefaults();
    upsert Product_Setting__c.getOrgDefaults();
    upsert Summary_Setting__c.getOrgDefaults();
    upsert Summary_Validation__c.getOrgDefaults();
    upsert MatchingRatio_Setting__c.getOrgDefaults();

    // Try to kick off batch jobs in new install, for custom setting defaults already enabled as batch by default
    if (context != null && context.isUpgrade() == false) {
        // TODO: Josh is supposed provide Philip the job name to schedule - Nov 10, 2014
    }

    // Try to set values for dev org testing.
    if (context == null) {
        Account_Setting__c accountSetting = Account_Setting__c.getOrgDefaults();
        accountSetting.Rollup_Affiliations_Primary_Affiliation__c = 'NGO';
        update accountSetting;

        Pricebook2[] pricebooks = [SELECT Name FROM Pricebook2 WHERE Name = 'NGO'];
        if (pricebooks.isEmpty() == true) {
            upsert new Pricebook2 (Name = 'NGO', isActive = true);              
        }

        Opportunity_Setting__c opportunitySetting = Opportunity_Setting__c.getOrgDefaults();
        opportunitySetting.Default_Giving_Type_Engine__c = 'Standard';
        update opportunitySetting;

        Opportunity_Record_Type_Setting__c[] opportunityRecordTypeSettings = [SELECT Name FROM Opportunity_Record_Type_Setting__c];
        if (opportunityRecordTypeSettings.isEmpty() == true) {
            upsert new Opportunity_Record_Type_Setting__c[] {
                new Opportunity_Record_Type_Setting__c (
                    Name = 'Corporate Underwriting'
                    , Enable_NP__c = true
                    , Default_Page__c = 'Standard'
                    ),
                new Opportunity_Record_Type_Setting__c (
                   Name = 'Donation'
                   , Transaction_Name__c = 'Transaction'
                   , Enable_NP__c = true
                   , Giving__c = true
                   , Hard_Credits__c = true
                   , Default_Page__c = 'NP'
                   , Allocations__c = true
                   , Soft_Credits__c = true
                   , Giving_Type_Engine__c = 'Calendar Year'
                   ),
                new Opportunity_Record_Type_Setting__c (
                   Name = 'Grant'
                   , Enable_NP__c = true
                   , Default_Page__c = 'Standard'
                   ),
                new Opportunity_Record_Type_Setting__c (
                   Name = 'Inkind'
                   , Enable_NP__c = true
                   , Default_Page__c = 'Standard'
                   ),
                new Opportunity_Record_Type_Setting__c (
                   Name = 'Membership'
                   , Transaction_Name__c = 'Transaction'
                   , Enable_NP__c = true
                   , Giving__c = true
                   , Hard_Credits__c = true
                   , Summaries__c = true
                   , Default_Page__c = 'NP'
                   , Activity_Type__c = 'Membership'
                   , Allocations__c = true
                   , Soft_Credits__c = true
                   , Giving_Type_Engine__c = 'Standard'
                   ),
                new Opportunity_Record_Type_Setting__c (
                   Name = 'Outright Gift'
                   , Transaction_Name__c = 'Transaction'
                   , Enable_HE__c = true
                   , Giving__c = true
                   , Hard_Credits__c = true
                   , Default_Page__c = 'HE'
                   , Allocations__c = true
                   , Soft_Credits__c = true
                   ),
                new Opportunity_Record_Type_Setting__c (
                   Name = 'Pledge'
                   , Transaction_Name__c = 'Pledge Payment'
                   , Enable_HE__c = true
                   , Giving__c = true
                   , Hard_Credits__c = true
                   , Default_Page__c = 'HE'
                   , Allocations__c = true
                   , Soft_Credits__c = true
                   ),
                new Opportunity_Record_Type_Setting__c (
                   Name = 'Proposal'
                   , Enable_NP__c = true
                   , Default_Page__c = 'Standard'
                   ),
                new Opportunity_Record_Type_Setting__c (
                   Name = 'Purchase'
                   , Transaction_Name__c = 'Transaction'
                   , Enable_NP__c = true
                   , Giving__c = true
                   , Hard_Credits__c = true
                   , Default_Page__c = 'NP'
                   , Allocations__c = true
                   , Soft_Credits__c = true
                   )
            };
        }

        Apex_Jobs_Configuration_Setting__c[] apexJobsConfigurationSettings = [SELECT Name FROM Apex_Jobs_Configuration_Setting__c];
        if (apexJobsConfigurationSettings.isEmpty() == true) {
            upsert new Apex_Jobs_Configuration_Setting__c[] {
                new Apex_Jobs_Configuration_Setting__c (
                    Name = 'Account_UpdateOrdersSchedulable'
                    , Grouping_Type__c = ''
                    , PriceBookEntry_Id__c = ''
                    )
            };
        }

    }
}

/*******************************************************************************************************
* @description Static method that detects if we are running in a sandbox.
* @return true if running in sandbox. false otherwise.
********************************************************************************************************/
public static Boolean isSandbox() {
    return URL.getSalesforceBaseUrl().getHost().left(2).equalsignorecase('cs');
}

/*******************************************************************************************************
* @description Static method that returns the real email if it's a sandbox email.
* @return string with user's email.
********************************************************************************************************/
public static String sandboxEmailToRealEmail(String email) {
    if((isSandbox() || Test.isRunningTest()) && email.contains('=')) {
      email = email.substring(0, email.indexOf('@'));
      email = email.replace('=', '@');
    }
    return email;
}

/*
Comment - RCSBIRD-13616 Decision is to run the Apex batch job standalone fror data migration from Account
          to Matching_Ratio__c object.
public void runUpgradeScript() {
    try {
        Database.executeBatch(new Account_CreatePrimaryRatioBatchable());
    } catch(Exception e) {
        EmailSubject = EMAIL_SUBJECT_UPGRADE_ERROR;
        EmailMessage = EMAIL_MESSAGE_UPGRADE_SCRIPT_ERROR;
        sendErrorEmail(e, localContext,EmailSubject,EmailMessage);
    }
}
*/

public void sendErrorEmail(Exception e,System.InstallContext context,String subject,String message) {
    try {
        String userEmail;
        User user;
        if (context != null && context.installerID() != null) {
            user = [Select Id,Name,Email from User where Id = :context.installerID()];
        } else {
            user = [Select Id, Name ,Email from User where Id = :UserInfo.getUserId()];
        }

        if (user != null) {
            userEmail = user.Email;
        }
        userEmail = sandboxEmailToRealEmail(userEmail);
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        mail.setUseSignature(false);
        mail.setReplyTo('[email protected]');
        mail.setSenderDisplayName('xxxx');
        mail.setSubject(subject);
        String bodyString = message;
        bodyString += '\n\n Organization: '+ UserInfo.getOrganizationName() +' ('+ UserInfo.getOrganizationId() +')' ;
        if (user != null) {
            bodyString += '\n User: '+ user.Name + ' (' +user.id + ')' ;
        }
        bodyString += '\n Package: rC Giving ';

        if (e != null) {
            bodyString += '\n\n==========================. \n\n Error message: \n' + e.getMessage() + '\n\n'
            + 'Error stack trace: \n' + e.getStackTraceString();
        }
        mail.setPlainTextBody(bodyString);
        mail.setToAddresses(new String[]{'[email protected]', userEmail});
        Messaging.sendEmail(new Messaging.SingleEmailMessage[]{mail});
    } catch(Exception e2) {
        //Nothing to do here, as we don't want the install script to fail because emails could not be sent.
    }
}

}

3
  • NOTE: migrateMatchingRatioData method in onInstall is commented out as well as the method since it is not working, also runUpgradeScript() method, but it should be clear. Apr 10, 2015 at 16:10
  • I believe you issue may be with the Query for the user. try changing the filter from user info.getUserID() to a new variable set by the install handler using localcontext.installerID()
    – Eric
    Apr 10, 2015 at 16:21
  • Also, you may want to hardcode an email to be sent in the catch block. Have it sent to a specific email address and use no org specific information. Add the exception message and stacktracestring to the email
    – Eric
    Apr 10, 2015 at 16:23

0

You must log in to answer this question.

Browse other questions tagged .