Send Email with Generated PDF as attachment from Trigger – before Winter 16

There may be scenario in Salesforce that you need to send a Visualforce page rendered as PDF as a part of Email Attachment. This will be very easy if you want to perform this using Controller or Extension class, we just have to call getContentAsPDF() method of PageReference class and use returned blob data as a attachment in Email.

Update – 21 Oct 2015 (Winter 16)

After Winter 16 release, this solution will not work as getContent() method is treated as callout and if we try to call it from Async Apex or Rest API in this case, it will not return pdf content.

This solution will only work for you if you have not enabled critical update “PageReference getContent() and getContentAsPDF() Methods Treated as Callouts” in your Salesforce organization.

Note : If this solution is not working for you then try this.

If we are talking about achieving same in Trigger then it would be problem. Trigger does not support getContent() method of PageReference class. If you are thinking to use getContent() in future call then again we are not lucky, because @future methods does not support it. Also Apex job doesn’t support this method.

Now, I hope you understood that in which situation we are 🙂

So, In this article, I am going to explain how to resolve this issue. Not exactly resolve but workaround for above problem.

Solution is very simple, We will expose apex method as a REST API. Code for sending email will be written in APEX based REST API and our Trigger will call this method asynchronously using @future annotation.

Sample Visualforce Page, page Name – “PDF_Demo

 <apex:page renderAs="PDF" >
   <!-- Begin Default Content REMOVE THIS -- >

<h1 >Congratulations </h1 >

  This is your new Page
   <!-- End Default Content REMOVE THIS -- >
 </apex:page >

Lets assume that above Visualforce page needs to be send as a part of attachment.

Creating APEX based REST API with POST Method :

/**
*	Created By	:	Jitendra Zaa
*	Description	:	Apex based REST API which exposes POST method to send Email
*/
@RestResource(urlMapping='/sendPDFEmail/*')
Global class GETPDFContent{
     @HttpPost
    global static void sendEmail(String EmailIdCSV, String Subject, String body) {

 	List&amp;lt;String&amp;gt; EmailIds = EmailIdCSV.split(',');

        PageReference ref = Page.PDF_DEMO;
        Blob b = ref.getContentAsPDF();

        Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();

        Messaging.EmailFileAttachment efa1 = new Messaging.EmailFileAttachment();
        efa1.setFileName('attachment_WORK.pdf');
        efa1.setBody(b);

        String addresses;
        email.setSubject( Subject +String.valueOf(DateTime.now()));
        email.setToAddresses( EmailIds  );
        email.setPlainTextBody(Body);
        email.setFileAttachments(new Messaging.EmailFileAttachment[] {efa1});
        Messaging.SendEmailResult [] r = Messaging.sendEmail(new Messaging.SingleEmailMessage[] {email});

    }
}

In above code, we have created APEX based REST API with POST method. We are simply getting content of Visualforce rendered as PDF with method “getContentAsPDF()” and setting up this in object of “Messaging.EmailFileAttachment”.

REST API created using Apex class can be accessed from URL “https://<YOUR_SALESFORCE_INSTANCE>/services/apexrest/<RESTURL>”

In below part of code, we will be consuming REST API recently created

/**
*	Created By	:	Jitendra Zaa
*	Description	:	This class exposes @future method to send VF page rendered as PDF as attachment in Email
*/
public class SendVFAsAttachment{

    @future(callout=true)
    public static void sendVF(String EmailIdCSV, String Subject,String body,String userSessionId)
    {
        //Replace below URL with your Salesforce instance host
        String addr = 'https://shivasoft--shiva1.cs6.my.salesforce.com/services/apexrest/sendPDFEmail';
        HttpRequest req = new HttpRequest();
        req.setEndpoint( addr );
        req.setMethod('POST');
        req.setHeader('Authorization', 'OAuth ' + userSessionId);
        req.setHeader('Content-Type','application/json');

        Map&amp;lt;String,String&amp;gt; postBody = new Map&amp;lt;String,String&amp;gt;();
        postBody.put('EmailIdCSV',EmailIdCSV);
        postBody.put('Subject',Subject);
        postBody.put('body',body);
        String reqBody = JSON.serialize(postBody);

        req.setBody(reqBody);
        Http http = new Http();
        HttpResponse response = http.send(req);
    }
}

Note : Please make sure you have added URL of your salesforce in “Remote Site Settings“, else you will not be able to make REST API call.

Question : How do we set Body for POST method in Apex based REST API or How do we supply argument in POST method ?
Answer : As you can see in above code sample, we have to supply JSON as a Request Body and set Header ContentType as “application/json“.

        req.setHeader('Content-Type','application/json');

        Map&amp;lt;String,String&amp;gt; postBody = new Map&amp;lt;String,String&amp;gt;();
        postBody.put('EmailIdCSV',EmailIdCSV);
        postBody.put('Subject',Subject);
        postBody.put('body',body);
        String reqBody = JSON.serialize(postBody);

Writing Trigger:

/**
*   Created By  :   Jitendra Zaa
*   Description :   This Trigger calls @future method to send VF page rendered as PDF as attachment in Email
*   Note        :   Make sure that this Trigger does not call Future methods more than 10 times.
*/
trigger SampleTrigger on Lead (before insert) {
    SendVFAsAttachment.sendVF('emailaddress1@email.com,emailaddress2@email.com','Sample Test from Trigger',
    'Sample Email Body',UserInfo.getSessionId());
}

From above sample code, we are calling static method asynchronously, which in turn calling REST API to send Email with Visualforce as attachment.

Posted

in

by


Related Posts

Comments

49 responses to “Send Email with Generated PDF as attachment from Trigger – before Winter 16”

  1. Naved Avatar
    Naved

    Hello Siva this is a wonderful stuff.

    But when i trying to use that then it shows an error in getContentAsPDF.

    Could you help to solve this error?

    My pdf page is

    ////////////////////////////////////////////////////

    @page {

    size:landscape;

    }

    @page {size: landscape;}

    @page {

    @top-center {

    content: element(header);

    }

    @bottom-left {

    content: element(footer);

    }

    }

    div.header {

    padding: 10px;

    position: running(header);

    }

    div.footer {

    display: block;

    padding: 5px;

    position: running(footer);

    }

    .pagenumber:before {

    content: counter(page);

    }

    .pagecount:before {

    content: counter(pages);

    }

    div.vertical-line{

    width: 100%; /* Line width */

    background-color: black; /* Line color */

    height: 1px; /* Override in-line if you want specific height. */

    float: left;

    /* Causes the line to float to left of content. You can instead use position:absolute if this fits better with your design */

    }

    @media print {

    tr.page-break { display: block; page-break-before: always; }

    }

    Prepared On:

    You agree to borrow the Loan on the terms and conditions set out in this document and the Borrower Agreement. You agree that

    the “Loan Contract” comprises this document, the Borrower Agreement and the Disclosure Statement, and that the Loan Contract sets

    out the terms and conditions for repayment of the Loan.

    {!objLoanApp.loan__Interest_estimated__c}

                     

                      

    The first payment will be made one month after the date of advance. The date of advance is the date on which payment of the advance

    is credited to your nominated bank account or otherwise applied on your behalf. The last payment is due «Loan_Term» months after

    the date of advance. All payments are to be made in accordance with this Schedule of Payments.

    alert(‘hi’);

    {!cases.peer__Loan_Account__r.loan__Account__r.Name}

    {!cases.Bank_Name__c}

    {!cases.Bank_Code__c}

    {!cases.peer__Bank_Account__c}

    {!cases.peer__Reference__c }

    {!cases.peer__Loan_Account__c}

    The Credit Contracts and Consumer Finance Act 2003 gives you a right for a short time after the terms of this contract have been

    disclosed to you to cancel the contract.

    How to cancel

    If you want to cancel this contract you must give written notice to the creditor.

    You must also return to the creditor any advance and any other property received by you under the contract.

    Time limits for cancellation

    If the disclosure documents are sent to you by electronic means, you must give notice that you intend to cancel within 5 working days

    after this electronic communication is sent.

    What you may have to pay if you cancel

    If you cancel the contract the creditor can charge you—

    (a) the amount of any reasonable expenses the creditor had to pay in connection with the contract and its cancellation (including legal

    fees and fees for credit reports, etc); and

    (b) interest for the period from the day you received the advance until the day you repay the advance.

    This statement only contains a summary of your rights and obligations in connection with the right to cancel. If there is anything about

    your rights or obligations under the Credit Contracts and Consumer Finance Act 2003 that you do not understand, if there is a dispute

    about your rights, or if you think that the creditor is being unreasonable in any way, you should seek legal advice immediately.

    ////////////////////////////////////////////////////////////////
    class –
    I do all my stuff in Constructor

    Thanks in advance

    1. JitendraZaa Avatar
      JitendraZaa

      Try to remove code from constructor and place inside action method of page.

      1. sham Avatar
        sham

        Great stuff.I implemented and it works fine.The only problem is session id.I have used this with controller.And i am scheduling the controller method.Till here it is clear.But when i m not logged in into instance the scheduler class doesn’t work.I am passing session id from developer console.Any other way to achieve this? Will be very helpful.

        1. Jason Avatar
          Jason

          Hi sham – Where you able to find fix for this? I too am having this issue. Thanks Jason

  2. Ricky Hewitt Avatar
    Ricky Hewitt

    Great tut! Just used this to solve a similar limitation where a scheduled class is unable to use the getContent() method! If you heading to DreamForce this year I’ll gladly buy you a beer 🙂

    1. JitendraZaa Avatar
      JitendraZaa

      Ha ha ha.. Thanks Ricky, I am glad it helped you.

      1. Ricky Hewitt Avatar
        Ricky Hewitt

        Just a quick note about my implementation with a scheduled class. Ran into an issue where the scheduled class runs as system so no user session is available. Had to refactor and ended up going with the trigger method defined here and passing in the user session. Again, thanks for the great tutorial. Saved my bacon!

        1. Jason Avatar
          Jason

          Hi Ricky, would you mind explaining how you passed in the user session. I too am having this issue. Thanks!

        2. yeswanth Avatar
          yeswanth

          hi ricky i need to implement using Email service i mean attach a pdf file in email and send email to salesforce then it should create a record in salesforce please send me one example for this

  3. dagny Avatar
    dagny

    This is great mate this solved my issue thanks a ton
    => My requirement was:
    Generate pdf, create record Attach this pdf to the record and send email notification every month through batch class.
    As the getContent doesn’t support even in the batch class HTTP REST API solved my problem.
    This Document is so descriptive.
    thanks again mate

    1. JitendraZaa Avatar
      JitendraZaa

      I am glad it helped you !!!

  4. Gilberto Olivo Avatar
    Gilberto Olivo

    hi, how i can send a parameter for the page that are pdf ? if it is possible?

    PageReference ref = Page.QW_HOJASERVICIO;

    ref.getParameters().put(‘id’, ‘500Z0000008na4N’);

    Blob b = ref.getContent();

    i’m trying to use in your code but doesn’t work, I use: system.debug(response); to view the response and the response are returning a error.

  5. Gilberto Olivo Avatar
    Gilberto Olivo

    how i write the test classes for your classes above ?

  6. Saikat Neogy Avatar
    Saikat Neogy

    I am trying to copy the code of Apex based REST API but getting below error . Can someone help me here?

    unexpected token: ‘sendEmail’ at line 4 column 37

  7. pavanpavan Avatar
    pavanpavan

    This is super post boss… Thanks a lot

  8. Anmol Avatar
    Anmol

    Hi Jitendra,
    This is great article but we have a situation where we have a public site and if change is made in public site by guest user then the pdf generated is blank. If the same pdf is generated by logged in sf user by clicking the same button then the pdf is generated perfectly. Could you please help us on this.

    1. Karan Avatar
      Karan

      Hi Anmol, I have a same situation where public site guest user has to get an email with PDF attached. Did you find any workaround for this?

      Any help would be greatly appreciated. Thanks!

      1. EDavis Avatar
        EDavis

        Hi, all,

        Originally, we were using Jitendra’s solution and it was working perfectly, for both an authenticated user and the unauthenticated site guest user. However, as Jitendra mentioned:
        (1.) this solution no longer works with the “getContent() and getContentAsPdf() are treated as callouts” update (originally scheduled for Summer ’15, but can currently still be activated)
        http://releasenotes.docs.salesforce.com/en-us/summer15/release-notes/rn_vf_getcontent_callout_cruc.htm?edition=&impact=
        http://docs.releasenotes.salesforce.com/en-us/spring16/release-notes/rn_vf_pagereference_getcontent_cruc.htm

        (2.) BUT this solution is also no longer needed after Winter ’16 because getContent() and getContentAsPdf() can be called directly from the future method, removing the need for the additional Rest service class.
        http://releasenotes.docs.salesforce.com/en-us/winter16/release-notes/rn_apex_pagereference_getcontent.htm

        We were able to move our logic for generating the Attachment and attaching it to the record from the REST class to the future method, and it worked for a logged-in user. HOWEVER, we are now having the same issue Anmol mentioned: when the site guest user calls the method, a blank pdf is generated.

        We’re still working on a solution. It looks like it does work to store information needed for the Attachment (the parent record Id, the html/pdf content) in a temporary object (a custom object we call Temporary Document), then run a scheduled job (as a system user) that processes these records every hour. The scheduled job calls a future method that:
        -instantiates the Visualforce page
        -passes the Temporary Document Id as a parameter to the page
        -calls PageReference.getContentAsPdf()
        -builds the Attachment
        -sends the email

        After the Temporary Document is processed and the Attachment is created, delete the Temporary Document. That might be a viable workaround, if you’re ok with your emails going out every hour? Any thoughts? Reasons we should be wary of this approach?

        Emily

    2. Biao Avatar
      Biao

      Same situation here

  9. S_Peterson Avatar
    S_Peterson

    Is there a way to show chatter files as images on the visualforce page?

    1. Jitendra Zaa Avatar

      Can you please explain more about your requirement ?

      1. s_peterson Avatar
        s_peterson

        I’m trying to create a visualforce email template that would display pictures the users took, (attached to a custom object, using chatter files) I figured out how to pull images from ‘Notes and Attachments’ but I can’t figure out how to get the images from chatter files. Here is the code I used in the email template that worked for
        attachments:

        Any ideas?

        1. Jitendra Zaa Avatar

          This will work:

          SELECT ContentDocument.LatestPublishedVersionId FROM ContentDocumentLink WHERE LinkedEntityId = :selectedId

          then

          To download the actual content, the URL is

          /sfc/servlet.shepherd/version/download/{!file.ContentDocument.LatestPublishedVersionId}

          Note: /sfc/servlet.shepherd is not officially supported or documented. This functionality can change without warning from release to release.

          1. S_Peterson Avatar
            S_Peterson

            Thanks for responding. I’m getting the error “Unknown property ‘core.email.template.EmailTemplateComponentController.file’”

            I’m very new at visualforce so I’m sure I’m doing something wrong when trying to create the email template:
            Here is what I changed

            List shares = [SELECT ContentDocument.LatestPublishedVersionId FROM ContentDocumentLink WHERE LinkedEntityId = :selectedId];

  10. SQ Avatar
    SQ

    With the latest summer 15 release, getContentAsPDF() is treated like callout method. So this example will break, any other workarounds that you can think of?

    1. Jitendra Zaa Avatar

      Thats interesting, Can you please point me to documentation ? I tried, but not able to go see it.

    2. Jitendra Zaa Avatar

      After Winter 16 release, you do not even need this solution. Because getContent() and getContentAsPdf() both are allowed in Asynch apex. That means you can use it in @future or Queueable interface

  11. Nf Avatar
    Nf

    Hi Jitendra,

    Thanks for very helpful post. At least now I’m getting attachment with my email. But the only issue is its just displaying labels and not actual data in attachment. The visualforce page which needs to be attached requires id, so I used below code.

    PageReference ref = Page.PA_Detail;
    ref.getParameters().put(‘id’,id);
    Can you help to resolve

    1. Jitendra Zaa Avatar

      Label means you are only able to get static data and not merge field values ?

      1. nf Avatar
        nf

        right no data.

  12. Amit Kumar Avatar
    Amit Kumar

    Hi Jitendra,

    I applied your piece of codes in my org but I got an error “Callout loop not allowed” I think this is because the new feature of salesforce “PageReference getContent() and getContentAsPDF() Methods Treated as Callouts” So could you help to over come this error.

    1. Jitendra Zaa Avatar

      After Winter 16 release, you do not even need this solution. Because getContent() and getContentAsPdf() both are allowed in Asynch apex. That means you can use it in @future or Queueable interface.

      1. Shine Avatar
        Shine

        Hi the auto-activation date has been postponed until Spring ’16 and after that the issue will still be present. Will your solution work after the activation?

  13. Arpit Avatar
    Arpit

    We also have the same requirement where we are calling the future method through a trigger, which will invoke the Web Service and that web service will send the email with VF page as attachment.

    But now due to summer 15 release changes getContentAsPDF() will behave as PDF.Please find the below link:

    https://help.salesforce.com/apex/HTViewSolution?id=000213972&language=en_US

    Now we are getting the error as “Callout loop not allowed”.

    So could you help as overcome this error.

    Thank you

  14. gopi Avatar
    gopi

    Hi Jitendra,

    Could you please tell me the steps which we have to do. I copied the code but not getting anything.

  15. sarbjeet heera Avatar

    I am also trying to do this type of work but not Attach e-mail i want to generate vf page as pdf for every employee and then attach that as pdf to each employee according to the Id but every time after attachment it is blank becuase of getcontentaspdf() not worjing in batch class and for this any solution,
    i have a Batch class
    global class RunPdfApex implements database.batchable{
    Public string queryString;

    Public void setQry(string queryString){
    this.queryString = ‘SELECT Id,Employee__c,Effective_From_Date__c,Basic_Salary__c,Dearness_Allowance_DA__c,House_Rent_Allowance_HRA__c,Income_tax_IT__c,Medical_Allowance_MA__c,Net_Salary__c,Provident_Fund_PA__c,Active__c FROM Salary__c WHERE Active__c=:true’;
    }
    global List start(database.batchableContext bc){

    List objList = [SELECT Id,Employee__r.Name,Effective_From_Date__c,Basic_Salary__c,Dearness_Allowance_DA__c,House_Rent_Allowance_HRA__c,Income_tax_IT__c,Medical_Allowance_MA__c,Net_Salary__c,Provident_Fund_PA__c,Active__c FROM Salary__c WHERE Active__c=:true];
    //return database.getquerylocator(queryString);
    return objList;
    }
    global void execute(database.batchablecontext bc, List objList){

    for(Salary__c Sal : objList)
    {
    PageReference pdf = new PageReference(‘https://c.ap2.visual.force.com/apex/SalarySlip?Id=’+Sal.Employee__c);
    pdf.setRedirect(true);
    Blob pdfPageBlob;
    pdfPageBlob = pdf.getContentAsPDF();
    system.debug(pdf);
    Attachment a = new Attachment();
    a.Body = pdfPageBlob;
    a.ContentType = ‘application/pdf’;
    a.parentId=Sal.Employee__c;
    a.Name = ‘Salary of’+ Sal.Employee__c +’.pdf’;
    a.Description = ‘Salary slip of ‘+ System.now().format(‘yyyy_MM_dd_hh_mm_ss’) + ‘.pdf’;
    insert a;
    }

    }
    Public void finish(database.batchableContext bc){
    }
    }
    then salary controller class for vf page
    public class SalaryController {
    public Contact employee{get;set;}
    public Salary__c salary {get;set;}

    public SalaryController(ApexPages.StandardController sc) {
    this.employee = (Contact)sc.getRecord();
    salary = [SELECT Id,Employee__r.Name,Employee__c,Effective_From_Date__c,Basic_Salary__c,Dearness_Allowance_DA__c,
    House_Rent_Allowance_HRA__c,Income_tax_IT__c,Medical_Allowance_MA__c,Net_Salary__c,
    Provident_Fund_PA__c,Active__c FROM Salary__c WHERE Active__c=:true
    AND Employee__c =: employee.Id];

    }

    }

    then vf page

  16. SfdcPro Avatar
    SfdcPro

    Hello Jitendra Sir,

    I have a requirement in which when record gets approved in approval process, On final approval action an email sends to an user.

    I have a visualforce page which is rendered as PDF I want that visualforce page attached to that email.

    How can I achieve this.

    Please help me out through this.

    1. Naga M Avatar
      Naga M

      Hi SFDC Pro,
      I need to implement the same scenario can you guide me if you know.

    2. Naga M Avatar
      Naga M

      Hi Sfdcpro,
      Please share the code if you for this implementation

  17. Sanjeev Kaurav Avatar
    Sanjeev Kaurav

    Hello Jitendra,

    I tried to apply the logic and it is working absolutely fine for my Visualforce Page , but as soon as I give an extension to my page with a controller the functionality doesnt work.Do I have to make some changes regarding my controller?

  18. Raviteja Avatar
    Raviteja

    Just want to know that this counts to API Call out right ?!!

    1. Jitendra Zaa Avatar

      If you are using this solution, then it will count as API because REST API is involved here.

  19. Nisha Avatar
    Nisha

    Hi,
    I am also facing the issue where PDF getting generated from VF page are blank.
    I do not have the corresponding critical update activated to treat getContentAsPDF() Methods as Callouts.
    The method getContentAsPDF() is invoked from a method SendPDF on custom controller class. And this SendPDF method is getting called as an action from a VF page.
    Below is the code of this method. After upsert Survey, trigger class gets invoked on Survey object.

    public static void SendPDF()
    {
    String AccountId = ApexPages.currentPage().getParameters().get(‘id’);
    String SurveyId = ApexPages.currentPage().getParameters().get(‘survey’);

    Survey__c[] Survey = [ SELECT Id, Name, Status__c, Send_PDF__c
    FROM Survey__c
    WHERE Id = :SurveyId LIMIT 1 ];

    if (Survey[0].Status__c.equals(‘Complete’) &&
    (Survey[0].Send_PDF__c == null || ! Survey[0].Send_PDF__c.equals(‘Sent’)))
    {
    PageReference pdfReport = Page.SurveyPg;

    pdfReport.getParameters().put(‘id’, AccountId);
    pdfReport.getParameters().put(‘survey’, SurveyId );
    //System.debug(pdfReport);

    Blob PDFBlob = pdfReport.getcontentAsPdf();
    String PDFName = ‘Survey’ + Survey[0].Name + ‘.pdf’;

    Attachment attachment = new Attachment();
    attachment.Body = PDFBlob;
    attachment.Name = PDFName;
    attachment.ParentId = SurveyId;
    insert attachment;

    Survey[0].Send_PDF__c = ‘Sent’;
    upsert Survey;
    }
    }

    We are not sending the PDF as email but instead attaching it to the survey record.
    I am not directly calling the method from trigger and also do not have the critical update activated but still facing the issue.
    Is there something else I need to do to make sure that getContentAsPDF() works correctly?

      1. Nisha Avatar
        Nisha

        Hi Jitendra, Thanks for your reply!
        I was able to figure out an issue in my extension class which was causing the context of the record to get lost. After correcting that code the PDF is now getting generated as expected.
        So currently I am calling the SendPDF method (which in turn calling getcontentAsPdf method) mentioned earlier from VF page.
        Just to make sure that the solution will work when the critical updates are forced on by salesforce, I activated the relevant Critical update and tested the functionality on sandbox (which is on winter 17). To my surprise it is still working correctly, I increased the log level just to check if if it is causing an additional callout but the callout number used is still 0 in the log. Am I missing something here? Isn’t it is expected to fail if called from a normal method?
        Sorry if I am asking very basic question but I am new to Salesforce Development and just want to make sure that I am doing test in correct way. I do not the functionality to fail after October 17 2016 once the critical update is auto activated by salesforce. Can you please help me clarifying my understanding on this?

  20. Gourav verma Avatar
    Gourav verma

    Hi,

    Is there any way to download lightning component as pdf? Or you can say that I want to achieve above process in lightning? any thing will be helpful.

    Thanks
    Gourav Vema

    1. Jitendra Zaa Avatar

      Unfortunately Lex component doesn’t support it yet. I don’t think any library would work as well because of locker service

  21. Nagesh Meesala Avatar
    Nagesh Meesala

    Hi Jitendra, I really appreciate your hard work and your motive to share your knowledge. I ran into a situation where a Batch Job is invoking a trigger and this trigger is calling a future method. So it threw an error. In order to avoid this problem, I added a if condition to call Non future method from Trigger in Batch context. So far good. But This Non Future method uses getpdfcontent() method and since a trigger is involved, it threw an exception. So what is the solution in my case.

  22. Kyle Schwartz Avatar

    Very helpful Salesforce insights!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from Jitendra Zaa

Subscribe now to keep reading and get access to the full archive.

Continue Reading