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
-
Navigate to the Azure Portal and go to App registrations.
-
Click New registration and give the application a meaningful name.
-
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 Microsoftcommonauthority, so this option is required for personal accounts to sign in.
-
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 typicallyhttp://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. -
Click Register. On the Overview tab, note the Application (client) ID.
-
Go to Certificates & secrets, create a new client secret, and copy its Value immediately, because Azure shows the value only once.
-
Go to API permissions, click Add a permission > Microsoft Graph > Delegated permissions, and add:
Mail.Readto read the user's emailUser.Readto read the signed-in user's profileoffline_accessso Microsoft returns a refresh token (Squid refreshes access tokens automatically)openidandemail
Delegated
Mail.Readcan 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
-
Navigate to the Connectors tab in the Squid Console.
-
Click Available Connectors, find the Outlook connector, and select Add Connector.
-
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
- Navigate to the Studio tab in the Squid Console.
- 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".
- Click Add Abilities, scroll to the SaaS section, and select the Outlook connector you created. Learn more about abilities here.
- 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.
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.
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:
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.
// 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:
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:
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
/meendpoints 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.