Donnerstag, 14. April 2016

Emails mit APEX | SINGLE_EMAIL_LIMIT_EXCEEDED

Email-Versand mit Visualforce und Apex kann schon manchmal Kopfschmerzen bereiten....

Entweder ist die Email zu groß, oder zu viele Attachments oder der HeapSize "beschwert sich" oder, wenn letztendlich alles läuft,  man wird von den Salesforce Limits zurück zum Ausgangspunkt katapultiert.

Das kann doch nicht so schwer sein!
Ist es auch nicht, wenn man das schon einmal gemacht hat ;-)

Habe eine Visualforce Maske zum Versenden von HTML Emails programmiert.
Diese werden als SingleEmailMessage gesendet:
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });

Sobald die Anzahl der pro Tag gesendeten Emails den Salesforce Limit "knackt", wird dem User die entsprechende Meldung eingeblendet.



Die auf die Org bezogene Limitierung lässt sich mit LIMITS.getLimitEmailInvocations() berechnen.

Hier ein Auszug aus den "Execution Governors and Limits"
https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits.htm

A Dev Edition org has a single email limit of 10 messages. (assuming 1 recipient per email)
A non Dev org has a single email limit of 1000 messages. (assuming 1 recipient per email)

Using the API or Apex, you can send single emails to a maximum of 1,000 external email addresses per day based on Greenwich Mean Time (GMT).


/*+++++++++++++++++++++++++++++++++++++++++
    sendApexMail
    */
 public pagereference sendApexMail(){
        Pagereference pr;
        transient Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        // Set the Subject and Body
        mail.setSubject(mailSubject);
        mail.setHtmlBody(mailHtmlBody);
        String sToMailBCC = '';
        String sReplyToEmail = UserInfo.getUserEmail(); //userEmail;
        // To: or Cc:
        set<String> setToMail = new set<String>();
        set<String> setCcMail = new set<String>();
        if(mailTo.contains(','))    setToMail.addAll(mailTo.split(','));
        else                        setToMail.add(mailTo);
        if(selectedAdditionalRecipient.trim() != ''){
            if(selectedAdditionalRecipient.contains(','))   setCcMail.addAll(selectedAdditionalRecipient.split(','));
            else                                            setCcMail.add(selectedAdditionalRecipient);
        }
        mail.setToAddresses(new list<String>(setToMail));
        // CC:
        if(!setCcMail.isEmpty()) mail.setCcAddresses(new list<String>(setCcMail));
        // BCC:
        if(sToMailBCC != '') mail.setBccAddresses(new String[] {sToMailBCC});
        // Reply To:
        mail.setReplyTo(sReplyToEmail);
        // Attachments (only active)
        Decimal totalSize = 0;
        set<Id> setIdAtt = new set<Id>();
        for(ParentAttachment pa:lstParentAttachments){
            if(pa.isActive){
                totalSize += pa.att.BodyLength;
                setIdAtt.add(pa.att.id);
            }
        }
        // Append Attachments
        transient List<Messaging.Emailfileattachment> lstFileAttachments = new List<Messaging.Emailfileattachment>();
        transient list<Attachment> lstAttachmentsToClone;
        if(!setIdAtt.isEmpty()){
            lstAttachmentsToClone = new list<Attachment>([select Name, Body, ParentId from Attachment where id in:setIdAtt OR Description=:objQuote.Id]);
            for(Attachment attach :lstAttachmentsToClone){
                Messaging.Emailfileattachment efa = new Messaging.Emailfileattachment();
                efa.setFileName(attach.Name);
                efa.setBody(attach.Body);
                lstFileAttachments.add(efa);
            }
        }
        if(!lstFileAttachments.isEmpty())
            mail.setFileAttachments(lstFileAttachments);
        // Send Email
        try{
            //Messaging.reserveSingleEmailCapacity(2);
            List<Messaging.SendEmailResult> emailSendResults;
            transient Decimal decHeap = Limits.getHeapSize();
            transient Decimal decHeapMax = 5570000; // <-- tested successfully with this size. max Heap Size is 6MB, but createActivity() uses heap size, too
            transient Double dblAllowedSize = 2.5;
            if(decHeapMax - decHeap < 0){
                ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Info, LABEL.QFX_message_EmailNotSent));
                ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Info, LABEL.QFX_message_AllowedSize + ' ' + dblAllowedSize.format() + ' MB'));
            }else{
                try{
                    String emailErrorReport;
                    emailSendResults = Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
                    for( Messaging.SendEmailResult currentResult : emailSendResults ) {
                        if( currentResult.getErrors() != null ) {
                            for( Messaging.SendEmailError currentError : currentResult.getErrors()){
                                emailErrorReport = emailErrorReport + '(' + currentError.getStatusCode() + ') ' + currentError.getMessage() + '\r' ;
                            }
                        }
                    }
                    System.debug(emailSendResults);
                    if(emailErrorReport == null){
                        createActivity(contactSendTo, objQuote.Id, mailSubject, mailHtmlBody, lstAttachmentsToClone);
                        // View Quote
                        pr = backToQuote();
                    }else{
                        ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, emailErrorReport));
                    }
                }catch(System.HandledException he){
                    ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, 'HandledException: ' + he.getMessage()));
                }catch(Exception e){
                    String sMsg = LABEL.QFX_message_SendEmailFailedLimit;
                    sMsg = sMsg.replace('%LIMIT%' , String.valueof(LIMITS.getLimitEmailInvocations()));
                    ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, sMsg));
                }
            }
        }catch (Exception e) {    ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, e.getMessage()));   }
        return pr;
    }

Mittwoch, 24. Februar 2016

Dynamic SOQL - Auswertung der Felder vom Related Object

Ein Custom Object ist mit dem Standard Account Object über ein Lookup-Feld verknüpft.
Abhängig vom Country ISO Code auf dem Account wird die Sprache auf dem Projekt automatisch gesetzt.
Da die oben beschriebene Konfiguration ein Teil eines Managed Packages ist, und die Country ISO Codes nicht in jeder Org konfiguriert sind, muss die Logik mit Einsatz von "dynamic soql" implementiert werden.

Bei der Auswertung der ISO Codes erscheint die Fehlermeldung:
Invalid field Account__r.BillingCountryCode for sf42_prfxpe__Project__c

Lösung: zuerst Account über die Relation definieren, dann das entsprechende Feld auswerten
sObject objRelAccount = objProject.getSObject('Account__r');

String sSoql = 'SELECT Id, Name, Account__c, Account__r. BillingCountryCode FROM Project__c';



for (Project__c p : Database.query(sSoql)){

   sObject objRelAccount = objProject.getSObject('Account__r');

   if(objRelAccount != null){

       String sIsoCode = (String)objRelAccount.get('BillingCountryCode');

   }

}