2

I have run into yet another problem due to restrictions placed by Salesforce.

So apparently we cannot make a call to a Visualforce page using getContent() from within a Apex trigger.

Currently we have an apex trigger that is executed when some new records are created and it sends emails to the portal users to let them know that new records are on the portal.

Now we also need to send pdf attachments to these users having the same look and feel as the portal page. My VF page were already designed to be rendered as pdfs so I figured I could make a getContent() call and store the reponse as a blob and attach it to the email object but it looks I can't do that.

Next I tried doing this from within the trigger.

                    HttpRequest request = new HttpRequest();
                    request.SetMethod('POST');
                    request.SetEndPoint('my URL');

                    HttpResponse httpResponse = new Http().Send(request);
                    body = httpResponse.GetBodyAsBlob();

Apparently even this does not work as callouts are not allowed from triggers!

So I read up on Scheduled APEX and wrote a class that implements Schedulable. getContent() does not work in scheduled APEX either so I now try to see if callouts work.

I created a method in this class with the @future annotation. I noticed that the httpResponse.GetBodyAsBlob(); was returning null. I verified that my URL was correct by directly accessing it in browser(Page showed up fine).

I then changed the method to POST. This time there is some data returned and my attachments were created. But the data seems to be messed up as adobe refuses to open these files plus their size seems to suggest something went wrong(<1kb).

This is how I am setting the content type to pdf.

                  Messaging.EmailFileAttachment attach = new Messaging.EmailFileAttachment();
                    attach.setContentType('application/pdf');
                    attach.setFileName(inv.Name+'.pdf');
                    attach.setInline(false);
                    attach.Body = body;
                    emailAttachList.add(attach);

So what can I do here? I then read this https://stackoverflow.com/questions/9404751/why-are-html-emails-being-sent-by-a-apex-schedulable-class-being-delivered-with

But I have more or less done the same thing...What am I doing wrong? Can someone please suggest a solution or workaround so I can access my VF page within an apex trigger or class and get the content to send it as a pdf attachment.

EDIT When I look at my debug logs I see that my GET request returns a 302 which must mean that its redirecting to the Salesforce login page. How do I modify my GEt request witjh authentication to the VF page?

                HttpRequest request = new HttpRequest();
                request.SetMethod('GET');
                request.SetEndPoint('my URL');

                HttpResponse httpResponse = new Http().Send(request);
                body = httpResponse.GetBodyAsBlob();
  • This looks almost identical to what this question was trying to do salesforce.stackexchange.com/questions/14042/… but if your goal is just to send a VF page as an attachment, you can do that using Workflow – Phil Hawthorn Jul 22 '13 at 15:40
  • Phil I did look at that solution but was unable to get it to work. I am not wellversed with workflows. We have to choose between 3 VF pages based on the type of the record. Also the email has some text and the atachment will be the VF page rendered as a PDF. Can this be achieved? Thanks! – Richard N Jul 22 '13 at 17:35
  • I think so, i've had a go at explaining as an answer. – Phil Hawthorn Jul 22 '13 at 18:12
2

For a low/no code solution, you could play around with something like this:

Step 1: Create a Picklist on your custom object (lets call this Template_c on object Invoice_c) which will be used to dictate the Email attachment content, e.g. Picklist values

  • Email Template 1
  • Email Template 2
  • Email Template 3

Step 2: Create your Email template as a Visualforce template, only rendering the relevant content for your attachment, for example:

<messaging:emailTemplate subject="Invoice - {!relatedTo.Name}" recipientType="Contact" relatedToType="Invoice__c">
    <messaging:attachment renderAs="pdf" filename="{!relatedTo.Name} rendered="{!relatedTo.Template__c == 'Email Template 1'}">  
        <c:MyVFForTemplate1/>    
    </messaging:attachment>
    <messaging:attachment renderAs="pdf" filename="{!relatedTo.Name} rendered="{!relatedTo.Template__c == 'Email Template 2'}">    
        <c:MyVFForTemplate2/>    
    </messaging:attachment>
    <messaging:attachment renderAs="pdf" filename="{!relatedTo.Name} rendered="{!relatedTo.Template__c == 'Email Template 3'}">    
        <c:MyVFForTemplate3/>    
    </messaging:attachment>
    <messaging:htmlEmailBody >
        Dear Customer,

        Here is your Invoice.  Please pay now, or else!
    </messaging:htmlEmailBody>
</messaging:emailTemplate>

(note, i have illustrated the example using a VF component but you could just as well copy/paste the VF page content into the Email template.

Step 3: Create a workflow rule with the following attributes:

  • Object = Invoice (Invoice__c)
  • Evaluate the rule when a record is: created, and any time it’s edited to subsequently meet criteria
  • Run this rule if the following criteria are met Template__c = 'Email Template 1' or 'Email Template 2' or 'Email Template 3'
  • 'Save & Next'

Step 4: Add a new Workflow Action: New Email Alert, selecting the Email template you just created

Step 5: Enable/Activate your workflow

Step 6: Update your Invoice record's Picklist value to one of the relevant picklist values and the workflow should fire (as a result of Step 3) sending an email (as a result of Step 4) in the format required (as a result of Step 2).

I haven't tested the above, as I've just typed it directly in here but this approach works as I have used it before.

|improve this answer|||||
  • Thanks so much Phil. It looks like this could work. The only thing is that the recipient email comes from a field on the Account object(Every customer has one account and one field containing this email). Is there any way I can access that field here and set it as the To: address?Thanks! – Richard N Jul 22 '13 at 18:42
  • The downside is that you'd have to have some reference to a Contact on the Invoice__c record. You could also try a field type of 'Email Field' on the Invoice (but I think it has to be on the Invoice) but I'm not sure how the recipientType in the email template would behave with that. I haven't time to test it i'm afraid, but it definitely works with a Contact lookup. – Phil Hawthorn Jul 22 '13 at 18:52
  • Yes Phil. You are right. It looks like the email recipient needs to be present on an email field on the object which uses the workflow So I can't access the email fields on the Accounts...rats! – Richard N Jul 22 '13 at 19:18
  • You could try using a workflow 'field update' to auto-populate the email field on the object that you're sending. Failing that, I would have thought a trigger to auto populate it would be less code than what you're trying to do with the callout (which btw, might be failing because you're not authenticating your web service call) – Phil Hawthorn Jul 22 '13 at 19:28
  • Yeah good times :) I have issues no what which approach...I tried hard to look at an example of making a GET request by passing a SF user/password but did not find out. The 302 response shows that it wants be to pass in authentication information as well. – Richard N Jul 22 '13 at 19:37
2

Note, this won't work as

The getContent and getContentAsPDFPageReference methods cannot be used in methods with the future annotation. Source


Calling an @future method from the trigger should work. However, you should give some consideration to how this will work with multiple records being processed in the trigger.

I think you should stick with an HTTP GET rather than a POST to get the PDF Report content.

For the future method, try something like:

PageReference pdf = new PageReference('your URL here');
// Alternatively, 
//PageReference pdf = Page.YourVisualforcePageName

// potentially add any query string parameters here
pdf.getParameters().put('id',accountId);

// the contents of the report in pdf form
Blob body;

try {

  // returns the output of the page as a PDF
  body = pdf.getContent();
} catch (VisualforceException e) {
  body = Blob.valueOf('PDF Get Failed');
}

Messaging.EmailFileAttachment attach = new Messaging.EmailFileAttachment();
attach.setContentType('application/pdf');
attach.setFileName('reportPdf.pdf');
attach.setInline(false);
attach.Body = body;

Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
mail.setUseSignature(false);
mail.setToAddresses(new String[] { email });
mail.setSubject('Report PDF Email Demo');
mail.setHtmlBody('Important Report Attachment!');
mail.setFileAttachments(new Messaging.EmailFileAttachment[] { attach }); 

// Send the email
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });

You should be able to test this out from anonymous Apex to exclude the trigger restrictions on pulling down content.

If the PDF you are emailed appears invalid or too small then it is likely something other than a PDF was returned by the getContent() call. Try debugging the content or converting the extension to .txt first. It will likely contain an HTML error message or similar.

|improve this answer|||||
  • 1
    Thanks Daniel. But I think I tried something like this. It did not work when used in the Trigger. Also the docs mention that getContent() and getContentasPDF() do not work with @future. salesforce.com/us/developer/docs/apexcode/Content/… Am I missing anything? – Richard N Jul 22 '13 at 14:30
  • 1
    Daniel, I updated the last part of my question. How do we perform an authenticated GET HttpRequest to the VF page? Maybe this may help me retrieve the page content. Thanks! – Richard N Jul 22 '13 at 14:36
  • 1
    Whoops, you are correct, getContent() can't be used from a future method. – Daniel Ballinger Jul 22 '13 at 19:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.