Outlook add-in is not getting access to downloading emails from Exchange

Jacob Brock-Hansen 1 Reputation point
2024-07-10T08:29:09.75+00:00

Description:

I have been working on an Outlook add-in from a template that is created using Yeoman Generator for Office Add-ins (javascript based and xml manifest). The idea is to make an easy way to download emails and store it in my organisations document system. This is mostly working, i can browse my email folders and list their displaynames, so i have access to something but if i try to download an email from the Exchange server i get an error:

"Resource not found for the segment 'messages'" or "Resource not found for the segment '<itemId>'"

Depending on if i use the me/ or not.

Im thinking theres is something wrong in my Azure App registration or my MSAL use somewhere but i cant quite figure out what, so now im asking for help here.

My setup:

Im running the web add-in locally on localhost:3000 - it is also deployed as an app service on azure to get a client id, and to be able to do an app registration

My local manifest file in the project have this bit:

<Permissions>ReadWriteMailbox</Permissions>

but nothing else regarding security or permissions.

I also have the client id from the app registration as my id in the manifest.

In the code im first requesting an accessToken with Msal:

import { PublicClientApplication } from "@azure/msal-browser";
const msalConfig = {
  auth: {
    clientId: "<my client id>",
    authority: "https://login.microsoftonline.com/common",
    redirectUri: "https://localhost:3000/auth.html",
  },
  cache: {
    cacheLocation: "localStorage",
    storeAuthStateInCookie: true,
  },
};
const msalInstance = new PublicClientApplication(msalConfig);
async function getAccessToken() {
    try {
        await msalInstance.initialize();
        const accounts = msalInstance.getAllAccounts();
        if (accounts.length === 0) {
            await msalInstance.loginPopup({
                scopes: ["openid", "profile", "Mail.Read", "Mail.ReadWrite"]
            });
        }
         const response = await msalInstance.acquireTokenSilent({
                scopes: ["openid", "profile", "Mail.Read", "Mail.ReadWrite"]            
                account: msalInstance.getAllAccounts()[0],
        });
        return response.accessToken;
    } catch (error) {
        console.error(error);
        if (error instanceof InteractionRequiredAuthError) {
            const response = await msalInstance.acquireTokenPopup({
                scopes: ["openid", "profile", "Mail.Read", "Mail.ReadWrite"],
            });
            return response.accessToken;
        }
    }
}

This is working as far as i can tell and i get an accessToken.

Next step would be to create a graph client to access my emails and download the file:

        const { Client } = require('@microsoft/microsoft-graph-client');
        async function getGraphClient(accessToken) {
            return Client.init({
                authProvider: (done) => {
                    done(null, accessToken);
                }
            });
        }
        try {
            const accessToken = await getAccessToken();
            console.log("Test accessToken " + accessToken);
            const emailId = Office.context.mailbox.item.itemId;
            console.log("Test emailId " + emailId);
            const client = await getGraphClient(accessToken);
            const response = await client.api("/me/mailFolders").get();
            const mailFolders = response.value;
            mailFolders.forEach(folder => {
                console.log(Folder Name: ${folder.displayName});
            });
            //const email = await client.api("/me/messages/" + emailId + "/$value").get();
            const email = await client.api("/messages/" + emailId + "/$value").get();
        } catch (error) {
            console.error('Error fetching and downloading email:', error);
        }

This is where i get the error Resource not found for the segment 'messages'

Im guessing that there might be something wrong with the setup in my app registration, but i have no idea what.

Its set up as a SPA (Single Page App).

In Authentication under "Select the tokens you would like to be issued by the authorization endpoint:" i checked both checkmarks

Access tokens

ID tokens

and i chose the multitenant in supported account types

In API Permissions i listed:

  • Mail.Read
  • Mail.ReadWrite
  • offline_access
  • openid
  • profile

All of them are delegated

I also added a client secret, but isn't using it for anything - not sure if that should be used somewhere.

I also exposed my API but not really doing much else in the app registration with that bit.

What to do?

Im hoping someone have an idea about what im doing wrong, all the guides i found online and via chatGTP suggest this setup should be the right one, im mostly worried that im missing something small somewhere

Anyway thank you for looking into this

Office Development
Office Development
Office: A suite of Microsoft productivity software that supports common business tasks, including word processing, email, presentations, and data management and analysis.Development: The process of researching, productizing, and refining new or existing technologies.
3,915 questions
Microsoft Entra
{count} votes

2 answers

Sort by: Most helpful
  1. Shweta Mathur 29,746 Reputation points Microsoft Employee
    2024-07-10T10:58:00.3533333+00:00

    Hi Jacob Brock-Hansen •,

    Thanks for reaching out.

    It seems that you are trying to access the Microsoft Graph API to read an email from the Exchange server, but you are getting an error "Resource not found for the segment 'messages'

    Based on the code you provided, it seems that you are using the correct scopes and permissions in your app registration to get the token.

    However, I noticed that you are using "/me/messages" in your API call, which is used to access the signed-in user's messages. It seems the endpoint you are using is not correct.

    To get all the messages in a user's mailbox:

    GET /me/messages
    GET /users/{id | userPrincipalName}/messages
    

    To get messages in a specific folder in the user's mailbox:

    GET /me/mailFolders/{id}/messages
    GET /users/{id | userPrincipalName}/mailFolders/{id}/messages
    
    
    

    Did you try to get all the messages

    let messages = await client.api('/me/messages').get();
    

    If you want to filter those, you can use

     let messages = await client.api('/me/messages') .select('sender,subject') .get();
    

    Hope this will help.

    Thanks,

    Shweta


  2. Jacob Brock-Hansen 1 Reputation point
    2024-08-05T15:57:24.05+00:00

    I finally found out what the problem is - pretty coincidently.

    I need to format the ItemId before using it... why doesn't this show up in any official documentation - i haven't read all of it, but i have loads of hours reading through it.

    The simple fix to all my issues:

    itemId = Office.context.mailbox.convertToRestId(itemId, Office.MailboxEnums.RestVersion.v1_0);
    
    

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.