Skip to main content

Microsoft Outlook

Connect Outlook to Squid to index and search a user's email with AI

The Outlook connector's functionality

The Outlook connector lets Squid AI agents read and search a user's Outlook mailbox using natural language. Each user connects their own mailbox through OAuth2, and Squid reads their mail through the Microsoft Graph API using delegated per-user access, so an agent only ever sees the email of the signed-in user.

The connector supports:

  • Listing a user's Outlook mail folders, including nested folders
  • Indexing a mail folder (or the inbox) into a knowledge base, with an optional cutoff date to limit how far back to read
  • Semantic search across a user's indexed email, exposed to agents as an ability
  • Listing which folders are indexed and how many emails each contains
  • Removing a folder and its indexed email from the knowledge base
  • Automatic background re-sync, so indexed folders stay current: new email is indexed, edited email is re-indexed, and deleted email is removed

Configuring the Outlook connector

Squid connects to Outlook using OAuth2 and the Microsoft Graph API. You register an application in Microsoft Entra ID (Azure Active Directory), then add an Outlook connector in the Squid Console using that application's credentials.

Creating a Microsoft Entra application

  1. Navigate to the Azure Portal and go to App registrations.

  2. Click New registration and give the application a meaningful name.

  3. Under Supported account types, choose who can connect their mailbox:

    • Select Accounts in this organizational directory only if only users in your Microsoft tenant (work or school accounts) will connect.
    • Select Accounts in any organizational directory and personal Microsoft accounts if users with personal accounts (for example @outlook.com) will connect. The connector uses the Microsoft common authority, so this option is required for personal accounts to sign in.
  4. Under Redirect URI, choose the Web platform and enter the exact URL your app redirects to after consent, for example https://your-app.com/outlook/callback. During local development this is typically http://localhost:5173/outlook/callback. This value must match the Redirect URL you set on the Squid connector and the URL your frontend sends in the consent request, character for character.

  5. Click Register. On the Overview tab, note the Application (client) ID.

  6. Go to Certificates & secrets, create a new client secret, and copy its Value immediately, because Azure shows the value only once.

  7. Go to API permissions, click Add a permission > Microsoft Graph > Delegated permissions, and add:

    • Mail.Read to read the user's email
    • User.Read to read the signed-in user's profile
    • offline_access so Microsoft returns a refresh token (Squid refreshes access tokens automatically)
    • openid and email

    Delegated Mail.Read can be granted by each user at consent time, so tenant admin consent is optional.

If you registered the app for work accounts only and later switch Supported account types to include personal accounts, Azure may reject the change with Property api.requestedAccessTokenVersion is invalid. Personal Microsoft accounts require v2.0 access tokens. Open the application's Manifest, set "requestedAccessTokenVersion": 2 inside the api block, save, and then change the account types.

Adding the Outlook connector to your Squid application

  1. Navigate to the Connectors tab in the Squid Console.

  2. Click Available Connectors, find the Outlook connector, and select Add Connector.

  3. Provide the following configuration:

    Connector ID: A string that uniquely identifies the connector in your code. Note this ID, because your code passes it when calling the connector.

    Client ID: The Application (client) ID from your Entra application.

    Client Secret: The client secret value you created in Certificates & secrets.

    Redirect URL: The redirect URI you registered on the Web platform above. It must match exactly.

Using the Outlook connector in your application

The indexing and listing operations run with your app's API key, so they must be called from backend code. Your frontend obtains the OAuth authorization code, then calls a backend executable that completes the connection and indexes mail. This keeps the API key off the client.

No-code Studio

  1. Navigate to the Studio tab in the Squid Console.
  2. Click Create AI Agent and give it an Agent ID and description, such as "outlook_agent" and "This agent answers questions about the user's email".
  3. Click Add Abilities, scroll to the SaaS section, and select the Outlook connector you created. Learn more about abilities here.
  4. Give a description of when the agent should use it, such as "Call this to search the user's Outlook email."

An agent can only search email that has already been indexed for a given user. Use the code flow below to connect a mailbox and index a folder first, then supply the same user identifier when the agent runs.

Basic building blocks in code

Install the client package:

npm install @squidcloud/outlook-client

Step 1: Redirect the user to Microsoft

Build the Microsoft consent URL and redirect the user. buildOutlookAuthUrl is a helper exported from the client package. Building a URL needs no API key, so this step is safe on the frontend. The identifier is any stable string that identifies the user in your app (for example their email). Squid stores their tokens under it and scopes indexing and search to it.

Client code
import { buildOutlookAuthUrl } from '@squidcloud/outlook-client';

// The redirect URI must exactly match the Web redirect URI registered in Azure
// and the Redirect URL configured on the Squid connector.
const redirectUri = `${window.location.origin}/outlook/callback`;

const consentUrl = buildOutlookAuthUrl({
clientId: 'YOUR_ENTRA_CLIENT_ID',
redirectUri,
state: 'user@example.com', // round-tripped back to your callback as `state`
});

window.location.href = consentUrl;

Step 2: Complete the connection on the backend

Microsoft redirects back to your callback with a code and the state you passed. Send both to a backend executable that exchanges the code for tokens with saveAuthCode and then indexes the inbox. Both operations use the app's API key, so they must run on the backend.

Backend code
import { executable, SquidService } from '@squidcloud/backend';
import { OutlookClient } from '@squidcloud/outlook-client';

// The Connector ID you set when adding the connector in the Console.
const OUTLOOK_CONNECTOR_ID = 'outlook';

interface ConnectOutlookRequest {
code: string;
identifier: string;
}

export class OutlookService extends SquidService {
// Exchange the Microsoft auth code for tokens, then index the user's inbox.
@executable()
async connectOutlook(request: ConnectOutlookRequest): Promise<void> {
const outlook = new OutlookClient(this.squid, OUTLOOK_CONNECTOR_ID);

// Store the user's tokens under their identifier.
await outlook.saveAuthCode(request.code, request.identifier);

// Index the inbox so an agent can search it. 'inbox' is a well-known folder name.
// Can use { sinceDate: '2024-01-01T00:00:00Z' } to only read mail on or after a cutoff.
const errors = await outlook.indexFolder('inbox', request.identifier);
if (errors.length > 0) {
console.error('Some emails failed to index:', errors);
}
}
}

Your callback route on the frontend reads the code and state from the URL and calls the executable:

Client code
const url = new URL(window.location.href);
const code = url.searchParams.get('code');
const identifier = url.searchParams.get('state');
if (code && identifier) {
// `squid` comes from your Squid client instance (for example the React SquidContext).
await squid.executeFunction('connectOutlook', { code, identifier });
}

Listing folders and managing what is indexed

Expose additional backend executables for the other operations. Each one constructs an OutlookClient and calls a single method.

Backend code
// List the user's Outlook folders so they can choose which to index.
@executable()
async getOutlookFolders(request: { identifier: string }) {
const outlook = new OutlookClient(this.squid, OUTLOOK_CONNECTOR_ID);
return await outlook.listFolders(request.identifier);
}

// Index a specific folder by id (or a well-known name such as 'inbox').
@executable()
async indexOutlookFolder(request: { folderId: string; identifier: string }) {
const outlook = new OutlookClient(this.squid, OUTLOOK_CONNECTOR_ID);
return await outlook.indexFolder(request.folderId, request.identifier);
}

// List which folders and emails are currently indexed for the user.
@executable()
async getIndexedOutlookItems(request: { identifier: string }) {
const outlook = new OutlookClient(this.squid, OUTLOOK_CONNECTOR_ID);
return await outlook.listIndexedItems(request.identifier);
}

// Remove a folder and all of its indexed emails from the knowledge base.
@executable()
async removeOutlookFolder(request: { folderId: string; identifier: string }) {
const outlook = new OutlookClient(this.squid, OUTLOOK_CONNECTOR_ID);
return await outlook.unindexFolder(request.folderId, request.identifier);
}

Searching email through an AI agent

Once a user's mail is indexed, connect the Outlook ability to an agent and ask questions. Set the user identifier so the agent searches only that user's email:

Backend code
const agent = this.squid.ai().agent('outlook_agent');
await agent.setAgentOptionInPath('connectedIntegrations', [
{
integrationId: 'outlook',
integrationType: 'outlook',
options: {
identifier: 'user@example.com',
},
},
]);

const answer = await agent.ask('Summarize my most recent emails about the Q3 budget');

You can also pass the identifier per call in the agent context, which overrides the default:

Backend code
const answer = await this.squid
.ai()
.agent('outlook_agent')
.ask('What did Dana say about the launch date?', {
agentContext: { identifier: 'user@example.com' },
});

Core concepts

  • Identifier: A stable string that identifies an end user, such as their email. Squid stores each user's OAuth tokens under their identifier and scopes indexing and search to it, so one connector can serve many users without mixing their mail. Use the same identifier across connect, index, and agent calls.
  • Delegated access: Squid reads mail through Graph /me endpoints using each user's own access token. An agent only sees the mail of the identifier it runs for.
  • Knowledge base: Indexing writes each email as a text context in a per-connector knowledge base. The agent's search ability runs a semantic search over these contexts, filtered to the user's identifier.
  • Background sync: The connector re-syncs indexed folders on a schedule. New mail is indexed, edited mail is re-indexed, deleted mail is removed, and a deleted folder is cleaned up. Re-indexing a folder reuses the cutoff date you supplied when you first indexed it.

Error handling and troubleshooting

unauthorized_client: The client does not exist or is not enabled for consumers You signed in with a personal Microsoft account, but the Entra application is registered for work or school accounts only. Either sign in with an account from the app's tenant, or change Supported account types to include personal Microsoft accounts. See Creating a Microsoft Entra application.

Property api.requestedAccessTokenVersion is invalid when changing supported account types Personal Microsoft accounts require v2.0 access tokens. Open the application's Manifest, set "requestedAccessTokenVersion": 2 inside the api block, save, and then change the account types. If Azure still rejects both changes together, save the token version first and change the account types in a second save.

The redirect fails or Microsoft reports a redirect URI mismatch The redirect URI must be identical in three places: the Web platform redirect URI in Azure, the Redirect URL on the Squid connector, and the URL your frontend sends in the consent request. A trailing slash or a different port counts as a mismatch. Confirm the URI is registered under the Web platform, not Single-page application, because the connector uses a confidential client token exchange.

FUNCTION_NOT_FOUND when calling an Outlook operation The Outlook connector has not been added to the app you are calling, or the Connector ID in your code does not match the one configured in the Console. Confirm the connector exists under the Connectors tab and that OUTLOOK_CONNECTOR_ID matches its Connector ID.

The agent replies that no emails are indexed Search only covers mail that has been indexed for that identifier. Connect the mailbox and index at least one folder first, and make sure the agent runs with the same identifier you indexed under.

Best practices

  • Keep the API key on the backend. Run saveAuthCode, indexing, and listing in backend executables, and have your frontend call those. Only the consent URL and the OAuth redirect belong on the client.
  • Use a cutoff date when indexing large mailboxes so you only read as far back as you need. The connector stores the cutoff and reuses it during background re-syncs.
  • Use a consistent identifier per user across connect, index, and agent calls. A mismatch is the most common reason an agent finds no email.
  • Rotate client secrets before they expire and add the new secret in the Console. Azure lets you keep more than one secret active, so you can rotate without downtime.
  • For OAuth token management details, see the External Authentication API.