3

Setup

Opportunities are related in a MDR to children__c object. When my opp reaches a stage, an email alert (via workflow) gets sent out with the opp info. I want to include a set of pdf attachments (1 for each child) related to that master opp. Question:

Can I do this for email alerts? Are custom components the way to go?

Linkage: http://www.salesforce.com/us/developer/docs/pages/Content/pages_email_templates_attachments.htm

1

4 Answers 4

7

If I'm understanding the question correctly, you can generally attach attachments to email sent out via workflow using the <messaging:attachment renderAs="PDF"> in your Email Template as per the documentation you supplied.

However, the problem you're going to have is the multiplicity of the attachments you want to create, you mention wanting to create many per Opportunity but the <messaging:attachment> cannot be a child of <apex:repeat>. If you didn't mind the data for each child being on separate pages you could do something like:

<messaging:emailTemplate subject="Test" recipientType="User" relatedToType="Opportunity">

<messaging:attachment renderAs="PDF">
    <html>
        <head/>
        <body>
            <apex:repeat var="line" value="{!relatedTo.Childrens__r}">
                <div style="page-break-after:always">{!line.Name}</div>
            </apex:repeat>
        </body>
    </html>
</messaging:attachment>

<messaging:plainTextEmailBody >
Congratulations!
This is your new Visualforce Email Template.
</messaging:plainTextEmailBody>
</messaging:emailTemplate>

Or, maybe you could actually fire the workflow alert from the Children__c object instead (this would result in multiple emails with one attachment though). Other than that, its roll your own as per the other answers I think.

If you have a finite number of children, you could always create a rollup summary on your Opportunity to count the children and do something like:

<messaging:attachment renderAs="PDF" rendered="{!relatedTo.ChildrenCount__c > 0}">
    <html>
    <head/>
    <body>
    <div style="page-break-after:always">{!relatedTo.Childrens__r[0]}.Name}</div>
    </body>
    </html>
</messaging:attachment>

<messaging:attachment renderAs="PDF" rendered="{!relatedTo.ChildrenCount__c > 1}">
    <html>
    <head/>
    <body>
    <div style="page-break-after:always">{!relatedTo.Childrens__r[1]}.Name}</div>
    </body>
    </html>
</messaging:attachment>

...but alas that's not very dynamic

2
  • Phil- i went with your first idea. thanks! the next thing is this- is there anyway i can get the std pagelayout automatically? I can't get <apex:detail> to work.. Commented Dec 4, 2013 at 18:48
  • @nivyaj I don't think you'll be able to do that, you'll have to roll your own in the body of the <messaging:attachment> (or create a Component to do it). Commented Dec 4, 2013 at 20:02
1

I am unsure if you can attach file(s) to emails sent from a workflow. However, if you decide to do so via apex code instead of the workflow, you can do so as follows:

// Create the data string that will become the csv body
public String csvBody() {
    String strOf = '\"';
    // Create the headers (these are not automatically added)
    for (String downloadField : downloadFields) {
        strOf += mapFieldLabels.get(downloadField) + '\",\"';
    }
    strOf += '\"\n';
    // Create the data rows (these are not automatically added)
    // "getFullResults()" is a Database.query() result cast to List<sObject>
    for (sObject result : getFullResults()) {
        strOf += '\"';
        for (String downloadField : downloadFields) {
            // If the field is a reference field, lookup the ".Name" version, otherwise this will simply populate the Id (you can still get the Id by requesting Account.Id instead of AccountId)
            // "field" is obtained dynamically since "Account.ParentId" does not work in a get() method
            if (mapSObjectFields.get(UtilsGeneral.getLookupSObjectName(strSObjectName, downloadField)).get(UtilsGeneral.getLookupFieldName(downloadField).toLowerCase()).getDescribe().getType() == DisplayType.Reference && UtilsGeneral.getRecursive(result, downloadField) != null) {
                if (downloadField.right(3) == '__c') {
                    downloadField = downloadField.left(downloadField.length() - 3) + '__r.Name';
                } else if (downloadField.right(2) == 'Id' && downloadField.length() > 2) {
                    downloadField = downloadField.left(downloadField.length() - 2) + '.Name';
                }
            }
            // populate the field only if != null (without this logic check, "null" will appear in the csv field)
            // the "getRecursive()" method is required since "Account.ParentId" does not work in a get() method... 
            if (UtilsGeneral.getRecursive(result, downloadField) != null) strOf += UtilsGeneral.getRecursive(result, downloadField);
            strOf += '\",\"';
        }
        strOf += '\"\n';
    }
    return strOf;
}

// Create an attachment from the string created above and attach it to an email and send
public void sendEmail() {
    String csvName = 'export';
    Messaging.EmailFileAttachment csvAttc = new Messaging.EmailFileAttachment();
    blob csvBlob = Blob.valueOf(csvBody());
    csvAttc.setFileName(csvName + '.csv');
    csvAttc.setBody(csvBlob);
    Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
    List<String> toAddresses = new List<String>{UserInfo.getUserEmail()};
    String subject = csvName;
    email.setSubject(subject);
    email.setToAddresses(toAddresses);
    email.setPlainTextBody('Attached you will find a csv containing the data you just requested from salesforce.com: ' + csvName + '.csv\n\nThank you,\n\nSales Operations');
    email.setFileAttachments(new List<Messaging.EmailFileAttachment>{csvAttc});
    List<Messaging.SendEmailResult> r = Messaging.sendEmail(new List<Messaging.SingleEmailMessage>{email});
}

mapSObjectFields is a Map<String, Map<String, Schema.SObjectField>> filled with the Schema.getGlobalDescribe().get(sObjectName).getDescribe().fields.getMap() for the sObject(s) you are querying (must also include the sObjects that are referenced dynamically within, i.e. if you are querying the Opportunity sObject and refer to Account.ParentId, mapSObjectFields must include the getMap() for both Account and Opportunity)

mapSObjectFieldLabels is a Map<String, Map<String, String>> filled with the Schema.SObjectField.getLabel() for the sObject(s) you are querying (same data as mapSObjectFields, but with getLabel() instead of the Schema.SObjectField as the result)

**If you need help with mapSObjectFields or mapSObjectFieldLabels, feel free to ask... The variable { get; set; } parameters were too hefty to post here...

The methods required to run this:

public static Object getRecursive(sObject record, String fieldName) {
    if(fieldName.contains('.')) {
        Integer firstPeriod = fieldName.indexOf('.');
        String nextSObjectName = fieldName.subString(0, firstPeriod);
        String nextfieldName = fieldName.subString(firstPeriod + 1, fieldName.length());
        sObject nextRecord = record.getSObject(nextSObjectName);
        if(nextRecord == null) {
            return null;
        } else {
            return getRecursive(nextRecord, nextfieldName);
        }
    } else {
        return record.get(fieldName);   
    }
}

public static String getLookupFieldName(String fieldName) {
    if (fieldName.contains('.')) {
        fieldName = fieldName.subString(fieldName.indexOf('.', fieldName.countMatches('.') - 1) + 1, fieldName.length());
    }
    return fieldName;
}

public static String getLookupSObjectName(String sObjectName, String fieldName) {
    String relationshipName = '';
    system.debug('getLookupSObjectName().start() = ' + sObjectName);
    if (fieldName.contains('.')) {
        Map<String, Schema.SObjectType> globalDescribe = Schema.getGlobalDescribe();
        Map<String, Map<String, Schema.SObjectField>> mapSObjectFields = new Map<String, Map<String, Schema.SObjectField>>();
        mapSObjectFields.put(sObjectName, new Map<String, Schema.SObjectField>());
        mapSObjectFields.get(sObjectName).putAll(globalDescribe.get(sObjectName).getDescribe().fields.getMap());
        for (Integer i = 0; i < fieldName.countMatches('.'); i++) {
            Integer firstCharacter = 0;
            if (i > 0) firstCharacter = fieldName.indexOf('.', i - 1) + 1;
            Integer lastCharacter = fieldName.indexOf('.', i);
            relationshipName = fieldName.subString(firstCharacter, lastCharacter);
            if (relationshipName.right(3) == '__r') {
                relationshipName = relationshipName.left(relationshipName.length() - 3) + '__c';
            } else {
                relationshipName = relationshipName + 'Id';
            }
            relationshipName = relationshipName.toLowerCase();
            sObjectName = mapSObjectFields.get(sObjectName).get(relationshipName).getDescribe().referenceTo[0].getDescribe().getName();
            fieldName = fieldName.subString(lastCharacter + 1, fieldName.length());
            if (!mapSObjectFields.containsKey(sObjectName)) {
                mapSObjectFields.put(sObjectName, new Map<String, Schema.SObjectField>());
                mapSObjectFields.get(sObjectName).putAll(globalDescribe.get(sObjectName).getDescribe().fields.getMap());
            }
        }
        system.debug('getLookupSObjectName().end() = ' + sObjectName);
    }
    return sObjectName;
}
0

It sounds like you probably want an apex batch job which is started by an apex trigger.

The apex batch job would create your pdf attachments related to the child objects and in the finish method of the batch you can send the email to the user.

0

Nivyaj, I don't believe you can attach things with an email alert.

You'll need a scheduled batch job to a: create the pdf's and b: send the emails with them.

This assumes you need to create the pdf's. if you do not need to create them, the you can probably just link to them? depending on where they're stored.

if you go down the scheduled batch job route you'll need two classes (in addition to your tests)

  1. batch job interface
  2. Scheduled apex interface

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .