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.

Related posts

  • 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

    • JitendraZaa

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

      • 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.

        • Jason

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

  • 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 🙂

    • JitendraZaa

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

      • 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!

        • Jason

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

  • 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

    • JitendraZaa

      I am glad it helped you !!!

  • 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.

  • Gilberto Olivo

    how i write the test classes for your classes above ?

  • 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

  • pavanpavan

    This is super post boss… Thanks a lot

  • 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.

    • 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!

      • 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

    • Biao

      Same situation here

  • S_Peterson

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

    • Can you please explain more about your requirement ?

      • 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?

        • 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.

          • 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];

  • 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?

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

    • 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

  • 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

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

      • nf

        right no data.

  • 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.

    • 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.

      • 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?

  • 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

  • gopi

    Hi Jitendra,

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

  • 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

  • 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.

  • 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?

  • Raviteja

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

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