Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

My first Add-In #5160

Closed
adumith opened this issue Dec 5, 2024 · 10 comments
Closed

My first Add-In #5160

adumith opened this issue Dec 5, 2024 · 10 comments
Assignees
Labels
Area: Outlook Issue related to Outlook add-ins Type: product feature request Office JS ideas that should be posted to aka.ms/m365dev-suggestions (formerly User Voice.)

Comments

@adumith
Copy link

adumith commented Dec 5, 2024

Hello everyone,

I’m working on developing my first Add-In for Outlook 365/Windows 10. In theory, it’s quite simple—it’s a drop-down menu with two options. Each option opens a new email and loads a different template, depending on the selection.

So far, the menu is functioning smoothly. Selecting option A displays the corresponding template, and the same goes for option B.

However, I’m facing a problem: I can’t get the user’s signature to load automatically as expected, even though the signature is properly configured and set as the default for new emails.

Attached you will find my manifest.xml and commands.ts
manifest.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<OfficeApp xmlns="http://schemas.microsoft.com/office/appforoffice/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0" xmlns:mailappor="http://schemas.microsoft.com/office/mailappversionoverrides/1.0" xsi:type="MailApp">
<Id>2eb95a7a-bc4a-4f53-bc6f-ba64b72d8663</Id>
<Version>1.0.0.0</Version>
<ProviderName>Contoso</ProviderName>
<DefaultLocale>en-US</DefaultLocale>
<DisplayName DefaultValue="NewClientMatter"/>
<Description DefaultValue="A template to get started."/>
<IconUrl DefaultValue="https://localhost:3000/assets/icon-64.png"/>
<HighResolutionIconUrl DefaultValue="https://localhost:3000/assets/icon-128.png"/>
<SupportUrl DefaultValue="https://www.contoso.com/help"/>
<AppDomains>
<AppDomain>https://www.contoso.com</AppDomain>
</AppDomains>
<Hosts>
<Host Name="Mailbox"/>
</Hosts>
<Requirements>
<Sets>
<Set Name="Mailbox" MinVersion="1.5"/>
</Sets>
</Requirements>
<FormSettings>
<Form xsi:type="ItemRead">
<DesktopSettings>
<SourceLocation DefaultValue="https://localhost:3000/taskpane.html"/>
<RequestedHeight>250</RequestedHeight>
</DesktopSettings>
</Form>
</FormSettings>
<Permissions>ReadWriteItem</Permissions>
<Rule xsi:type="RuleCollection" Mode="Or">
<Rule xsi:type="ItemIs" ItemType="Message" FormType="Read"/>
<Rule xsi:type="ItemIs" ItemType="Message" FormType="Edit"/>
</Rule>
<DisableEntityHighlighting>false</DisableEntityHighlighting>
<VersionOverrides xmlns="http://schemas.microsoft.com/office/mailappversionoverrides" xsi:type="VersionOverridesV1_0">
<Requirements>
<bt:Sets DefaultMinVersion="1.5">
<bt:Set Name="Mailbox"/>
</bt:Sets>
</Requirements>
<Hosts>
<Host xsi:type="MailHost">
<DesktopFormFactor>
<FunctionFile resid="Commands.Url"/>
<ExtensionPoint xsi:type="MessageReadCommandSurface">
<OfficeTab id="TabDefault">
<Group id="msgReadGroup">
<Label resid="GroupLabel"/>
<Control xsi:type="Menu" id="DropdownMenu">
<Label resid="DropdownMenu.Label"/>
<Supertip>
<Title resid="DropdownMenu.Label"/>
<Description resid="DropdownMenu.Tooltip"/>
</Supertip>
<Icon>
<bt:Image size="16" resid="MenuIcon.16x16"/>
<bt:Image size="32" resid="MenuIcon.32x32"/>
<bt:Image size="80" resid="MenuIcon.80x80"/>
</Icon>
<Items>
<Item id="InternalAction">
<Label resid="InternalAction.Label"/>
<Supertip>
<Title resid="InternalAction.Label"/>
<Description resid="InternalAction.Tooltip"/>
</Supertip>
<Icon>
<bt:Image size="16" resid="InternalIcon.16x16"/>
<bt:Image size="32" resid="InternalIcon.32x32"/>
<bt:Image size="80" resid="InternalIcon.80x80"/>
</Icon>
<Action xsi:type="ExecuteFunction">
<FunctionName>handleInternalAction</FunctionName>
</Action>
</Item>
<Item id="ExternalAction">
<Label resid="ExternalAction.Label"/>
<Supertip>
<Title resid="ExternalAction.Label"/>
<Description resid="ExternalAction.Tooltip"/>
</Supertip>
<Icon>
<bt:Image size="16" resid="ExternalIcon.16x16"/>
<bt:Image size="32" resid="ExternalIcon.32x32"/>
<bt:Image size="80" resid="ExternalIcon.80x80"/>
</Icon>
<Action xsi:type="ExecuteFunction">
<FunctionName>handleExternalAction</FunctionName>
</Action>
</Item>
</Items>
</Control>
</Group>
</OfficeTab>
</ExtensionPoint>
</DesktopFormFactor>
</Host>
</Hosts>
<Resources>
<bt:Images>
<bt:Image id="MenuIcon.16x16" DefaultValue="https://localhost:3000/assets/menu-icon-16.png"/>
<bt:Image id="MenuIcon.32x32" DefaultValue="https://localhost:3000/assets/menu-icon-32.png"/>
<bt:Image id="MenuIcon.80x80" DefaultValue="https://localhost:3000/assets/menu-icon-80.png"/>
<bt:Image id="InternalIcon.16x16" DefaultValue="https://localhost:3000/assets/int-16.png"/>
<bt:Image id="InternalIcon.32x32" DefaultValue="https://localhost:3000/assets/int-32.png"/>
<bt:Image id="InternalIcon.80x80" DefaultValue="https://localhost:3000/assets/int-80.png"/>
<bt:Image id="ExternalIcon.16x16" DefaultValue="https://localhost:3000/assets/ext-16.png"/>
<bt:Image id="ExternalIcon.32x32" DefaultValue="https://localhost:3000/assets/ext-32.png"/>
<bt:Image id="ExternalIcon.80x80" DefaultValue="https://localhost:3000/assets/ext-80.png"/>
</bt:Images>
<bt:Urls>
<bt:Url id="Commands.Url" DefaultValue="https://localhost:3000/commands.html"/>
<bt:Url id="Taskpane.Url" DefaultValue="https://localhost:3000/taskpane.html"/>
<bt:Url id="CommandsJs.Url" DefaultValue="https://localhost:3000/commands/commands.js"/>
</bt:Urls>
<bt:ShortStrings>
<bt:String id="GroupLabel" DefaultValue="New Client/Matter"/>
<bt:String id="DropdownMenu.Label" DefaultValue="Choose Action"/>
<bt:String id="InternalAction.Label" DefaultValue="Internal"/>
<bt:String id="ExternalAction.Label" DefaultValue="External"/>
</bt:ShortStrings>
<bt:LongStrings>
<bt:String id="DropdownMenu.Tooltip" DefaultValue="Select an action to perform."/>
<bt:String id="InternalAction.Tooltip" DefaultValue="Opens an internal email template."/>
<bt:String id="ExternalAction.Tooltip" DefaultValue="Opens an external email template."/>
</bt:LongStrings>
</Resources>
</VersionOverrides>
</OfficeApp>

commands.ts

/* global Office */
Office.onReady(function (info) {
if (info.host === Office.HostType.Outlook) {
Office.actions.associate("handleInternalAction", handleInternalAction);
Office.actions.associate("handleExternalAction", handleExternalAction);
}
});
/**
* Handles the "Internal" action.
* @param event The Office Add-in event.
*/
function handleInternalAction(event: Office.AddinCommands.Event): void {
openEmailTemplate("internal", event);
}
/**
* Handles the "External" action.
* @param event The Office Add-in event.
*/
function handleExternalAction(event: Office.AddinCommands.Event): void {
openEmailTemplate("external", event);
}
/**
* Opens an email template with the user's signature.
* @param templateType The type of template ("internal" or "external").
* @param event The Office Add-in event.
*/
function openEmailTemplate(templateType: string, event: Office.AddinCommands.Event): void {
let templateBody: string;
let subject: string;
if (templateType === "internal") {
templateBody = "This is the internal email template.";
subject = "Internal Email Subject"; // Modifica el asunto si es necesario
} else {
templateBody = `
This is the external email template.<br/><br/>`;
subject = "External Client/Matter"; // Asunto para correos externos
}
Office.context.mailbox.displayNewMessageForm({
subject,
htmlBody: templateBody,
});
setTimeout(() => {
Office.context.mailbox.item.body.getAsync(Office.CoercionType.Html, (result) => {
if (result.status === Office.AsyncResultStatus.Succeeded) {
const userSignature = result.value || "";
if (userSignature) {
const combinedBody = templateBody + "<br/><br/>" + userSignature;
Office.context.mailbox.item.body.setAsync(
combinedBody,
{
coercionType: Office.CoercionType.Html,
},
(setResult) => {
if (setResult.status === Office.AsyncResultStatus.Succeeded) {
} else {
console.error("Failed to set email body:", setResult.error);
}

event.completed();
}
);
} else {
console.warn("User's signature not available.");
event.completed();
}
} else {
console.error("Failed to get user's signature:", result.error);
event.completed();
}
});
}, 2000); 
event.completed();
} 

Thank you so much for your support!

@microsoft-github-policy-service microsoft-github-policy-service bot added the Needs: triage 🔍 New issue, needs PM on rotation to triage ASAP label Dec 5, 2024
@exextoc exextoc added Needs: attention 👋 Waiting on Microsoft to provide feedback Area: Outlook Issue related to Outlook add-ins and removed Needs: triage 🔍 New issue, needs PM on rotation to triage ASAP labels Dec 5, 2024
@exextoc exextoc self-assigned this Dec 5, 2024
@adumith
Copy link
Author

adumith commented Dec 10, 2024

@exextoc, I've been doing some research on this and someone told me the following:

Also note that you cannot concatenate two well-formed HTML documents and expect a valid HTML document as a result; the two must be merged..

Which makes a lot of sense because I'm essentially working with two HTML objects: the email template, which is created inside command.TS, and the signature, which is another HTML object.

@millerds
Copy link

Have you looked at the string that is being used to set the body to see if the result is good HTML?

Looking at the code I see that you are setting the body with new content. If I'm not mistaken that will overwrite the existing content including any signature that get's added . . . but I'm not certain on that. There may still be an issue with the signature getting inserted . . . I'm just not sure how that looks when you are replacing the body.

I also noticed that you are using a timer instead of a call back in displayNewMessageFrom . . . that might give you problematic results as it creates a race condition between the displayNewMessageForm and timer.

@adumith
Copy link
Author

adumith commented Dec 14, 2024

Hi, thank you so much for getting back to me.

You’re absolutely right in your assessment.

When the email loads, it adds the signature, but when the template is applied, the signature gets removed. This happens because I’m handling two separate HTML files. To address this, I merged both HTML files, which works perfectly in OWA. However, in the desktop version of Outlook, I’m encountering this issue: Uncaught TypeError: Office.context.mailbox.item.body.setAsync is not a function.

This error occurs because the setAsync method isn’t available on the body object in the Outlook context, likely due to the type of object I’m working with. So, I had to change the process logic as follows: first, I merge both HTML files (signature and template); then, I create a blank email and paste the merged HTML into it. But now, I’m facing another issue: the function is grabbing whichever email I’m currently positioned on, opening it, and overwriting it with the merged HTML.

Any idea why this might be happening?

I’ve attached the function in case you’d like to take a look and provide some guidance.

`
/* global Office */

Office.onReady(function (info) {
if (info.host === Office.HostType.Outlook) {
Office.actions.associate("handleInternalAction", handleInternalAction);
Office.actions.associate("handleExternalAction", handleExternalAction);
}
});

/**

  • Handles the "Internal" action.
  • @param event The Office Add-in event.
    */
    function handleInternalAction(event: Office.AddinCommands.Event): void {
    prepareEmail("internal", event);
    }

/**

  • Handles the "External" action.
  • @param event The Office Add-in event.
    */
    function handleExternalAction(event: Office.AddinCommands.Event): void {
    prepareEmail("external", event);
    }

/**

  • Prepares the email template by combining the template and the user's signature.
  • @param templateType The type of template ("internal" or "external").
  • @param event The Office Add-in event.
    */
    function prepareEmail(templateType: string, event: Office.AddinCommands.Event): void {
    let templateBody: string;
    let subject: string;

// Step 1: Select the template
if (templateType === "internal") {
templateBody = Internal Template;
subject = "New Client/Matter Intake (Internal)";
} else if (templateType === "external") {
templateBody = External Template;
subject = "New Client/Matter Intake (External)";
} else {
alert("Invalid template type specified.");
event.completed();
return;
}

// Step 2: Retrieve the user's signature
Office.context.mailbox.item.body.getAsync(Office.CoercionType.Html, (result) => {
if (result.status === Office.AsyncResultStatus.Succeeded) {
const userSignature = result.value || "";
const combinedBody = <html> <head> <meta charset="utf-8"> </head> <body> ${templateBody} <br/><br/> ${userSignature} </body> </html>;

  // Step 3: Open a new email draft and set the combined content
  Office.context.mailbox.displayNewMessageForm({
    subject,
    htmlBody: combinedBody,
  });

} else {
  console.error("Failed to retrieve user's signature:", result.error);
  alert("Failed to retrieve user's signature.");
  event.completed();
}

});
}
`

Thanks in advance!

@millerds
Copy link

millerds commented Jan 2, 2025

When clicking the button you are making, your context is the current item displayed in read mode. So when you call "Office.context.mailbox.item" that is what you are getting in read mode, and you can't "setAsync" read mode content. Also, when you are calling "Office.context.mailbox.item.body.getAsync" you are getting the whole body of the current displayed email and not just the "signature" . . . so when you set the new email body you are including all the merged content which includes the entire content you retrieved from the existing email.

@adumith
Copy link
Author

adumith commented Jan 2, 2025

When clicking the button you are making, your context is the current item displayed in read mode. So when you call "Office.context.mailbox.item" that is what you are getting in read mode, and you can't "setAsync" read mode content. Also, when you are calling "Office.context.mailbox.item.body.getAsync" you are getting the whole body of the current displayed email and not just the "signature" . . . so when you set the new email body you are including all the merged content which includes the entire content you retrieved from the existing email.

Thank you very much for your explanation - that's exactly what's happening.

Could you help me modify the function to correct what you indicated?

I appreciated it so much.

@millerds
Copy link

millerds commented Jan 3, 2025

As far a generating a new email it looks like you're doing the right thing.

It's pretty un-reliable to try and get the signature from the body of the current email. You have to let Outlook insert the signature on new emails like it would for any other new email. Just call displayNewMessageForm with the template content (not the body of the current email merged into it) and let Outlook do it's job.

@adumith
Copy link
Author

adumith commented Jan 6, 2025

It's pretty un-reliable to try and get the signature from the body of the current email. You have to let Outlook insert the signature on new emails like it would for any other new email. Just call displayNewMessageForm with the template content (not the body of the current email merged into it) and let Outlook do it's job.
Hi Darren,

I hope you had a great holiday season with your family!

Thanks for all your help - it's really appreciated.

I've applied the changes you suggested, and if I understand correctly, this would be the result:

`/* global Office */
Office.onReady(function (info) {
if (info.host === Office.HostType.Outlook) {
Office.actions.associate("handleInternalAction", handleInternalAction);
Office.actions.associate("handleExternalAction", handleExternalAction);
}
});

/**

  • Handles the "Internal" action.
  • @param event The Office Add-in event.
    */
    function handleInternalAction(event: Office.AddinCommands.Event): void {
    openEmailTemplate("internal", event);
    }

/**

  • Handles the "External" action.
  • @param event The Office Add-in event.
    */
    function handleExternalAction(event: Office.AddinCommands.Event): void {
    openEmailTemplate("external", event);
    }

/**

  • Opens an email template.
  • @param templateType The type of template ("internal" or "external").
  • @param event The Office Add-in event.
    */
    function openEmailTemplate(templateType: string, event: Office.AddinCommands.Event): void {
    let templateBody;
    let subject;

// Define the template content
if (templateType === "internal") {
templateBody = Internal Template ;
subject = "Internal Template";
} else if (templateType === "external") {
templateBody = External Template;
subject = "External Template";
} else {
console.error("Invalid template type specified.");
event.completed();
return;
}

// Open a new email draft with the template content
Office.context.mailbox.displayNewMessageForm({
subject,
htmlBody: templateBody, // Provide the template body directly
});

// Event completion
event.completed();
}
`

Thank you again.

@adumith
Copy link
Author

adumith commented Jan 6, 2025

As far a generating a new email it looks like you're doing the right thing.

It's pretty un-reliable to try and get the signature from the body of the current email. You have to let Outlook insert the signature on new emails like it would for any other new email. Just call displayNewMessageForm with the template content (not the body of the current email merged into it) and let Outlook do it's job.

I forgot to tell you that applying your suggestion, the process added the template properly but missed the signature block.

Even having the signature set for new emails it's not pop up.

Thank you,

@millerds
Copy link

millerds commented Jan 7, 2025

Looks like when you specify the htmlBody that is used completely, and the signature is not added. I have logged and internal issue to consider this behavior.

I'm not sure what your total end goal is, but the OnNewMessageCompose event (which doesn't have the context of the current read item) can be used to insert a signature (see: https://learn.microsoft.com/en-us/samples/officedev/pnp-officeaddins/outlook-add-in-set-signature/), but you have to have the signature content to insert. Currently we don't have a way to get the existing signature settings and depend on the native or add-in signature insertion to get triggered.

@millerds millerds added Type: product feature request Office JS ideas that should be posted to aka.ms/m365dev-suggestions (formerly User Voice.) and removed Needs: attention 👋 Waiting on Microsoft to provide feedback labels Jan 9, 2025
@millerds
Copy link

millerds commented Jan 9, 2025

Looks like this is actually a design that has been that way for a while. If you want displayNewMessageForm to insert the signature after the content you specify in the htmlBody argument you'll have to make a feature request on Dev community:
https://aka.ms/M365dev-suggestions. Feature requests on Tech Community are considered, when we go through our planning process.

@millerds millerds closed this as completed Jan 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: Outlook Issue related to Outlook add-ins Type: product feature request Office JS ideas that should be posted to aka.ms/m365dev-suggestions (formerly User Voice.)
Projects
None yet
Development

No branches or pull requests

3 participants