Showing posts with label Email. Show all posts
Showing posts with label Email. Show all posts

Sep 12, 2025

Attach PDF file programmatically to Email and send

Let's see how PDF file is attached to an Email and sending programmatically. Lets see why this is important.

1) Email Templates cannot be created with an attachment. (Ironically!)
2) If we attach programmatically we can change the attachment based on logic if required.

In Summery, we can initiate Email in Draft state, then attach the PDF and then send.

Initiation and sending the e-mail part is explained in Programmatically create a draft Email using Email Template (C#).

Lets see the code

// Initiate Email in Draft state

// Create Attachment
byte[] embededPdf = LoadEmbeddedPdf("PluginProject.Resources.ApplicationForm.pdf")
Guid attachmentId = CreatePdfAttchment(OrgSvc,emailId, Convert.ToBase64String(embededPdf));

// Send Email

// Methods to use
private byte[] LoadEmbeddedPdf(string resourceName)
{
    var assembly = Assembly.GetExecutingAssembly(); 
    using (Stream stream = assembly.GetManifestResourceStream(resourceName))
    {
        if (stream == null)
            throw new FileNotFoundException("Embedded PDF file not found: " + resourceName);
        using (MemoryStream ms = new MemoryStream())
        {
            stream.CopyTo(ms);
            return ms.ToArray();
        }
    }
}

public Guid CreatePdfAttchment(IOrganizationService OrgSvc, Guid emailId, string base64Pdf)
{
    Entity attachment = new Entity("activitymimeattachment");
    attachment["subject"] = "Application Form";
    attachment["filename"] = "ApplicationForm.pdf";
    attachment["mimetype"] = "application/pdf";
    attachment["body"] = base64Pdf;
    attachment["attachmentnumber"] = 1;
    attachment["objectid"] = new EntityReference("email", emailId);
    attachment["objecttypecode"] = "email";
    return OrgSvc.Create(attachment);
}

There are two important things here;

1. PDF is saved within the Project and path needs to be passed correctly in below standard.

<Project Namespace><Folder><filename>.pdf

So you will realize when I pass PluginProject.Resources.ApplicationForm.pdf, my project namespace is  PluginProject, folder name is Resources and pdf file name is ApplicationForm.pdf.

2. Go to properties of the PDF file (In VS) and do below setting.
    


Email Template to populate dynamic values from any entity

Email Templates of Dynamics 365/ Dataverse is very useful yet its one big limitation is it only can dynamically populate values of a one Entity type. Word Document templates don't have that limitation but it is actually for generation of a document not to use in Email content. Alternatively, Word Document Templates can be used to generate a document and attach to email which is widely done.

Today we will see how to programmatically enhance the usual Email Template to have any value. Below are the steps we do.

1) When creating Email Template, introduce special tags for dynamics values we plan to populate from other entities

2) Create email with Template in Draft status.

3) Replace special tags we introduce (Template will have many other dynamic fields as usual from specific Entity type it designed to fetch data from)

4) Send the Email

Please refer Programmatically create a draft Email using Email Template (C#) for steps 1 and 3.

Lets check how code would flow with the newly introduced method. Obviously values you need to populate should be query first to replace the tags.

// Step 2: Create Draft Email
// email object is defined here

// Step 3: Replace special tags
ReplaceEmailContent(email, "{{Coordinator}}", <Value need to populate>);
ReplaceEmailContent(email, "{{ContractId}}", <Value need to populate>);

// Step 4: Send Email

// Method to call
public Entity ReplaceEmailContent(Entity email, string tagName, string tagValue)
{
    var secondValue = email.Attributes.Values.ElementAt(1);
    if (secondValue is string strValue)
    {
        var replaced = strValue.Replace(tagName, tagValue);
        var key = email.Attributes.Keys.ElementAt(1);
        email.Attributes[key] = replaced;
    }
    return email;
}

Same way, if we need to add dynamics tags to Subject of the email, that's also possible. For that, use below method.

public Entity ReplaceEmailSubject(Entity email, string tagName, string tagValue)
{
    var secondValue = email.Attributes.Values.ElementAt(0);
    if (secondValue is string strValue)
    {
        var replaced = strValue.Replace(tagName, tagValue);
        var key = email.Attributes.Keys.ElementAt(0);
        email.Attributes[key] = replaced;
    }
    return email;
}

Mar 14, 2025

Send Email with dynamic excel attachment

Sending an email with associated excel sheet with details of child records is a generic requirement and also great way to pass child record details to a customer. For example, you may need to send an email to a customer with quote details and associate quote products as a excel attachment. 

In my example I am sending email to a Account and Sender is a Queue (you may obviously use a System user). I am sending Opportunity details and associated excel will carry Opportunity Products. This Email will be shown in timeline of Opportunity hence regarding Object would be the Opportunity. 

I have two separate code snippets here.

1) Sending the Email with Template and Excel

What we need to understand is we should create the Email first and then send as two steps.. This way, we get a room prior to sending, to attach the excel. Note how we pass different Ids like Sender, Template, Receiver and Regarding Object here. Also notice that we pass the excel as the attachment body.

Public void SendEmailWithPaymentEvaluationDetails(Guid templateId, Guid toAccountId, Guid fromQueueId, Guid regOpportunityId)
{
    // Initiate Email
    InstantiateTemplateRequest request = new InstantiateTemplateRequest()
    {
        TemplateId = new Guid(templateId),
        ObjectId = invoiceRequest.Id,
        ObjectType = invoiceRequest.LogicalName
    };
    InstantiateTemplateResponse response = (InstantiateTemplateResponse)OrgService.Execute(request);
    Entity email = response.EntityCollection[0];
    Entity Fromparty = new Entity("activityparty");
    Entity Toparty = new Entity("activityparty");
    Toparty["partyid"] = new EntityReference("account", toAccountId);
    Fromparty["partyid"] = new EntityReference("queue", fromQueueId);
    email["from"] = new Entity[] { Fromparty };
    email["to"] = new Entity[] { Toparty };
    email["directioncode"] = true;
    email["regardingobjectid"] = new EntityReference("opportunity", regOpportunityId);
    Guid emailId = OrgService.Create(email);

    // Link the Attachment
    Entity attachment = new Entity("activitymimeattachment");
    attachment["subject"] = "OpportunityId Product List";
    attachment["filename"] = "OpportunityId Product List.xlsx";
    attachment["body"] = Convert.ToBase64String(CompileExcelFile(regOpportunityId));
    attachment["mimetype"] = "application/vnd.ms-excel";
    attachment["attachmentnumber"] = 1;
    attachment["objectid"] = new EntityReference(email.LogicalName, emailId);
    attachment["objecttypecode"] = email.LogicalName;
    OrgService.Create(attachment);

    // Send Email
    SendEmailRequest sendEmailRequest = new SendEmailRequest
    {
        EmailId = emailId,
        TrackingToken = string.Empty,
        IssueSend = true
    };
    SendEmailResponse sendEmailResponse = (SendEmailResponse)OrgService.Execute(sendEmailRequest);
}

2) Compilation of Excel

Here we compile the excel which is called in above method when preparing the attachment. 
In this technique, we need to have a view created in Opportunity Product and retrieve its Id to be used here. That's the Id you see in SavedQuery. Interestingly, though we assign an id of a saved view, we are defining dynamically what we need in the excel via our own Fetch query. Under grid section, we adjust the column widths etc.
 
public byte[] CompileExcelFile(Guid regOpportunityId)
{
    var exportToExcelRequest = new OrganizationRequest("ExportToExcel");
    exportToExcelRequest.Parameters = new ParameterCollection();
    exportToExcelRequest.Parameters.Add(new KeyValuePair<string, object>("View", new EntityReference("savedquery", new Guid("{4c523f5b-e8c5-4cb5-bc83-bf4ef934342d}"))));
    string stringFetchXml = @"<fetch distinct='false' no-lock='false' mapping='logical' returntotalrecordcount='true'>
                                <entity name='opportunityproduct'>
                                    <attribute name='lineitemnumber' />
                                    <attribute name='productname' />
                                    <attribute name='description' />
                                    <attribute name='baseamount' />
                                    <filter>
                                           <condition attribute='opportunityid' operator='eq' value='{0}' />
                                    </filter>
                                   </entity>
                            </fetch>";
    exportToExcelRequest.Parameters.Add(new KeyValuePair<string, object>("FetchXml", String.Format(stringFetchXml, regOpportunityId.ToString())));
    exportToExcelRequest.Parameters.Add(new KeyValuePair<string, object>("LayoutXml", @"
            <grid name='resultset' object='2' jump='lineitemnumber' select='1' icon='1' preview='1'>
                <row name='result' id='opportunityproductid'>
                    <cell name='lineitemnumber' width='100' />
                    <cell name='productname' width='200' />
                    <cell name='description' width='300' />
                    <cell name='baseamount' width='125' />
                </row>
            </grid>"));
    exportToExcelRequest.Parameters.Add(new KeyValuePair<string, object>("QueryApi", ""));
    exportToExcelRequest.Parameters.Add(new KeyValuePair<string, object>("QueryParameters", new InputArgumentCollection()));
    var exportToExcelResponse = OrgService.Execute(exportToExcelRequest);
    if (exportToExcelResponse.Results.Any())
        return exportToExcelResponse.Results["ExcelFile"] as byte[];
    else
        return null;
}

Hope this helps!

Feb 14, 2025

Programmatically create a draft Email using Email Template (C#)

Previously we discussed how to send an Email using a Email Template, but we noticed it just sends the email but no chance of create and save as a draft. In some instances we need to create the Draft email to be sent later after checking or/and modifications by the user. 

In such situations we can use below code. One can say its simply possible to use CREATE message of the Org service to achieve this but it is not possible to use a Template which is a constrain. 

By creating the draft first, it allows you to programmatically or manually attach attachments prior to sending the email.

Here InstantiateTemplateRequest  message does the magic.

 public void CreateDraftEmailToPrimaryContactOfAccount(Account account)
 {
     InstantiateTemplateRequest request = new InstantiateTemplateRequest()
     {
         TemplateId = new Guid("bf0b97c7-d5a3-4a3f-8771-a1cd737ab555"),
         ObjectId = account.Id,
         ObjectType = Account.LogicalName
     };
     InstantiateTemplateResponse response = (InstantiateTemplateResponse)OrgService.Execute(request);

     Entity email = response.EntityCollection[0];
     Entity Fromparty = new Entity("activityparty");
     Entity Toparty = new Entity("activityparty");
     Toparty["partyid"] = new EntityReference(Account.EntityLogicalName, account.PrimaryContactId.Id);
     Fromparty["partyid"] = new EntityReference("queue", new Guid("f14a45e9-fac5-4ba0-9a95-c07fe1adabf0"));
     email["from"] = new Entity[] { Fromparty };
     email["to"] = new Entity[] { Toparty };
     email["directioncode"] = true;
     email["regardingobjectid"] = new EntityReference(Account.EntityLogicalName, account.Id);
     Guid emailId = OrgService.Create(email);
}

When ready if you need to send the Email programmatically, below SendEmailRequest message can be used as below.

SendEmailRequest sendEmailRequest = new SendEmailRequest
{
     EmailId = emailId,
     TrackingToken = string.Empty,
     IssueSend = true
};
SendEmailResponse sendEmailResponse = (SendEmailResponse)OrgService.Execute(sendEmailRequest);

Related Posts
Programmatically send Email with Template (c#)

Programmatically send Email with Template (c#)

This is a code snippet that sends Email programmatically (in C#) while using an Email Template

Explained scenario, we send an Email to Primary Contact of the Account. We set Account as the regarding object of the Email so this Email activity will associate with Account and will be displayed in Account's timeline. Also we have to pass Account as the regarding object of Template since we want dynamic fields of Template to be filled with Account field values as necessary.

Actually SendEmailFromTemplateRequest message does the magic.

public void SendEmailToPrimaryContactOfAccount(Account account)
{
    Entity Fromparty = new Entity("activityparty");
    Entity Toparty = new Entity("activityparty");
    Toparty["partyid"] = new EntityReference(Contact.EntityLogicalName, account.PrimaryContactId.Id);
    Fromparty["partyid"] = new EntityReference("queue", new Guid("f14a45e9-fac5-4ba0-9a95-c07fe1adabf0"));
    Entity email = new Entity("email");
    email["from"] = new Entity[] { Fromparty };
    email["to"] = new Entity[] { Toparty };
    email["directioncode"] = true;
    email["regardingobjectid"] = new EntityReference(Account.EntityLogicalName, account.Id);

    SendEmailFromTemplateRequest emailUsingTemplateReq = new SendEmailFromTemplateRequest
    {
        Target = email,
        TemplateId = new Guid("bf0b97c7-d5a3-4a3f-8771-a1cd737ab555"),
        RegardingId = account.Id,
        RegardingType = Account.EntityLogicalName
    };
    var emailUsingTemplateRes = OrgService.Execute(emailUsingTemplateReq);
} 

Please note I am sending this email from a queue record. Obviously you can send from a system users as well. In such case, from activity party has to be changed accordingly.

One thing to note is, this method just sends the email, so you are not save Email in draft status.

Related Posts
Programmatically create a draft Email using Email Template (C#)

Apr 10, 2018

Configure Generic Email (i.e. Info@ABC.com) for Dynamics 365

In most cases, we need to have an Email address for general inquiries apart from user mailboxes. Let us see how we can do this with Dynamics 365 using Exchange Online.

1) Connecting to Exchange Online

To know how Exchange Online connects to Dynamics 365, please refer this post first.

2) Create Shared Mailbox

Now go to Office 365 Admin center and create a Shared Mailbox.


Actually shared Mailbox can be shared among users. When someone sends mail to this address, all the members would get the mail to a separate Mailbox. Any member can obviously reply from that. Interestingly, received mail will see it as it has come from Info@ address.

Though this is pretty cool, our intention here is not to use this sharing scenario, but to receive all the mails of Info@ address to a separate queue, so that any queue member can take action. In fact, we don’t add any member to this shared mailbox.


Anyway, in a while after we configure the shared mailbox, we should see the relevant entry in Exchange Admin Center as below.


3) Configure Queue in Dynamics 365

Now browse D365 Settings > Business Administration > Queue and open the Queue we are interested in and set Incoming Email with our shared Email. Also note that Convert Incoming Emails to Activities field is also important.



4) Configure Queue Mailbox in Dynamics 365

Now browse to D365 Settings > Email Configuration > Mailboxes and Open the Shared Mailbox and configure the Incoming and Outgoing emails for Server side sync as below. (Alternatively click Apply Default Email Settings if you have set Default by now. Click this for more information)
Now click Approve Email, Test and Enable Mailbox.


5) Check behaviour

a) Send an Email to Info@ address from outside. Then you will see this email in the Queue as below;


b) Now any member of the Queue can open the mail for replying.

Note;
Anyway when open the reply mail, by default, From field is getting populated with login user, but you need to select the Info@ account if the reply too should go with generic email address.


c) Here, we get the Reply mail too from Generic Info@ email


Apr 5, 2018

Configure Emails for Dynamics 365 Trial version

Once you get a Trial Dynamics 365, one of the most needed features would be Emails. Let’s see how to get it working.

1) Get Office 365 Enterprise E5

Since Exchange Online comes with Office 365 Enterprise E5, you need to get a trial version of it as well. For that, from Office 365 Admin Centre, browse to Purchase Services and get the trial.


2) Check if Exchange Server Online is Ready

Once refresh, you may see link Exchange under the Admin Centre. Once click it, it will open another window with Exchange Admin Center where you can click Mailboxes.


Well, you will not see any Mailbox at this point.


3) Assign Licence to Users to get Mailboxes

Now go to Users section of the Office 365 Admin Center where you can see all the active users. Once you open a User you will see the message “This user doesn’t have an Exchange Online License” under Mail Settings.


Then you have to click the Edit button for Product Licence as circled above where you have to enable a Licence for Office 365 Enterprise E5.


As you do this, you will see massage under the Mail Settings will change to below one.


This may take a while, but once complete the process, you will see those Mailboxes appear in Exchange Server Online Admin.


4) Check Active Email Server profile

Browse D365 to Settings > Email Configuration > Email Server Profiles.

Here, Microsoft Exchange Online profile should be already visible since Exchange and D365 are in same tenant. Make sure its Active.


5) Configure default email processing and synchronisation

Now browse to Setting > Administration > System Settings in D365 and go to Email Tab. Your configuration should look like below;


6) Configure Mailboxes in D365

Browse D365 Settings > Email Configuration > Mailboxes

Open the Mailbox you need to configure and do below steps;


a. Click Apply Default Email Settings button – This sets the default settings in Step 5.

b. Click Approve Email – You needs to be a Global Administrator to do that, Otherwise click Send Email for Approval which will request Approval from Global Admin.

c. Click Test & Enable Mailbox – This will take few minutes to Test the mail and enable the Mailbox.

7) Check if the Mailbox is correctly done

After all, you can Browse to the Alert section of the Mailbox to see the test result as below. These details show that testing is successful.


Same way, if there are issues, you can see the descriptions.

Feb 21, 2013

CRM 2011 E-mail Router - Download and Configuration

Just a quick post. Below is the place to download Microsoft Dynamics CRM 2011 E-mail Router.

http://www.microsoft.com/en-au/download/details.aspx?id=27818

And this video shows how to configure it, so that your users can use it for emails.


Hope this is helpful.