Outlook

Lightweight, dependency-free Microsoft Graph mail fake for local tests with @microsoft/microsoft-graph-client.

Defaults

SettingValue
Service nameoutlook
Default port4620
ProtocolHTTP, Microsoft Graph REST-style JSON
HealthcheckGET /_parlel/health
ResetPOST /_parlel/reset
Base URLhttp://127.0.0.1:4620/v1.0

Quick Start

import { OutlookServer } from "./services/outlook/src/server.js";
import { Client } from "@microsoft/microsoft-graph-client";

const server = new OutlookServer(4620);
await server.start();

const client = Client.init({
  baseUrl: "http://127.0.0.1:4620/v1.0",
  authProvider: (done) => done(null, "local-token"),
});

await client.api("/me/sendMail").post({
  message: {
    subject: "Hello from parlel",
    body: { contentType: "text", content: "Local only" },
    toRecipients: [{ emailAddress: { address: "agent@example.com" } }],
  },
  saveToSentItems: true,
});

const messages = await client.api("/me/mailFolders/sentitems/messages").get();
await server.stop();

Implemented Operations

Emulator

MethodEndpointNotes
GET/_parlel/healthReturns service status and object counts.
POST/_parlel/resetClears ephemeral state and restores default folders/categories.
GET/, /v1.0, /betaLightweight metadata response.

Users and Settings

MethodEndpointNotes
GET/v1.0/meReturns the current user.
GET/v1.0/users/{userId}Returns the requested local user.
GET/v1.0/me/mailboxSettingsReturns mailbox settings.
PATCH/v1.0/me/mailboxSettingsMerges mailbox setting changes.

Mail Folders

MethodEndpointNotes
GET/v1.0/me/mailFoldersLists top-level folders. Supports $top, $skip, $count, $filter, $search, $orderby, $select.
POST/v1.0/me/mailFoldersCreates a top-level folder.
GET/v1.0/me/mailFolders/{id}Gets a folder.
PATCH/v1.0/me/mailFolders/{id}Updates displayName.
DELETE/v1.0/me/mailFolders/{id}Deletes non-default folders.
GET/v1.0/me/mailFolders/{id}/childFoldersLists child folders.
POST/v1.0/me/mailFolders/{id}/childFoldersCreates a child folder.
GET/v1.0/me/mailFolders/{id}/childFolders/{childId}Gets a child folder.
PATCH/v1.0/me/mailFolders/{id}/childFolders/{childId}Updates a child folder.
DELETE/v1.0/me/mailFolders/{id}/childFolders/{childId}Deletes a child folder.

Default folders are inbox, drafts, sentitems, deleteditems, junkemail, archive, and outbox.

Messages and Drafts

MethodEndpointNotes
GET/v1.0/me/messagesLists messages. Supports $top, $skip, $count, $filter, $search, $orderby, $select.
POST/v1.0/me/messagesCreates a draft message by default.
GET/v1.0/me/messages/$countReturns a plain-text count.
GET/v1.0/me/messages/deltaReturns current messages plus @odata.deltaLink.
GET/v1.0/me/messages/{id}Gets a message. Supports $select and $expand=attachments.
PATCH/v1.0/me/messages/{id}Updates common mutable message fields.
DELETE/v1.0/me/messages/{id}Soft-deletes a message from visible results.
GET/v1.0/me/mailFolders/{folderId}/messagesLists messages in a folder.
POST/v1.0/me/mailFolders/{folderId}/messagesCreates a draft message in a folder.
GET/v1.0/me/mailFolders/{folderId}/messages/deltaFolder-scoped delta list.

Message Actions

MethodEndpointNotes
POST/v1.0/me/sendMailSends a new message and returns 202. Honors saveToSentItems: false by hiding the stored sent copy.
POST/v1.0/me/messages/{id}/sendSends a draft and moves it to sentitems.
POST/v1.0/me/messages/{id}/replyCreates a sent reply and returns 202.
POST/v1.0/me/messages/{id}/replyAllCreates a sent reply-all and returns 202.
POST/v1.0/me/messages/{id}/forwardCreates a sent forward and returns 202.
POST/v1.0/me/messages/{id}/createReplyCreates and returns a reply draft.
POST/v1.0/me/messages/{id}/createReplyAllCreates and returns a reply-all draft.
POST/v1.0/me/messages/{id}/createForwardCreates and returns a forward draft.
POST/v1.0/me/messages/{id}/moveMoves a message to destinationId and returns it.
POST/v1.0/me/messages/{id}/copyCopies a message to destinationId and returns the copy.

Attachments

MethodEndpointNotes
GET/v1.0/me/messages/{id}/attachmentsLists file attachments.
POST/v1.0/me/messages/{id}/attachmentsCreates a #microsoft.graph.fileAttachment.
GET/v1.0/me/messages/{id}/attachments/{attachmentId}Gets attachment metadata and contentBytes.
GET/v1.0/me/messages/{id}/attachments/{attachmentId}/$valueReturns raw attachment bytes.
DELETE/v1.0/me/messages/{id}/attachments/{attachmentId}Deletes an attachment.

Message Rules

MethodEndpointNotes
GET/v1.0/me/mailFolders/{folderId}/messageRulesLists message rules for a folder.
POST/v1.0/me/mailFolders/{folderId}/messageRulesCreates a message rule.
GET/v1.0/me/mailFolders/{folderId}/messageRules/{ruleId}Gets a message rule.
PATCH/v1.0/me/mailFolders/{folderId}/messageRules/{ruleId}Updates a message rule.
DELETE/v1.0/me/mailFolders/{folderId}/messageRules/{ruleId}Deletes a message rule.

Outlook Categories

MethodEndpointNotes
GET/v1.0/me/outlook/masterCategoriesLists categories.
POST/v1.0/me/outlook/masterCategoriesCreates a category.
GET/v1.0/me/outlook/masterCategories/{id}Gets a category.
PATCH/v1.0/me/outlook/masterCategories/{id}Updates a category.
DELETE/v1.0/me/outlook/masterCategories/{id}Deletes a category.

Subscriptions and Batch

MethodEndpointNotes
GET/v1.0/subscriptionsLists subscriptions.
POST/v1.0/subscriptionsCreates a subscription. Requires changeType, notificationUrl, and resource.
GET/v1.0/subscriptions/{id}Gets a subscription.
PATCH/v1.0/subscriptions/{id}Updates a subscription.
DELETE/v1.0/subscriptions/{id}Deletes a subscription.
POST/v1.0/$batchExecutes JSON batch requests against the in-process router.

Surface coverage

This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.

Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.

FeatureStatusNotes
@microsoft/microsoft-graph-client REST callsSupportedUse Client.init({ baseUrl, authProvider }). Auth tokens are accepted but ignored.
Microsoft Graph v1.0 and beta prefixesSupportedBoth route to the same fake implementation.
In-memory mail folders, messages, drafts, actions, attachmentsSupportedState is ephemeral and resettable.
OData paging/filter/search/order/select/countSupportedLightweight common subset for local tests.
Delta linksSupportedReturns current state plus a synthetic @odata.deltaLink.
Webhook subscription CRUDSupportedStored locally. No outbound HTTP notifications are sent.
JSON batchSupportedRoutes batch items in process.
Authentication, authorization, tenants, permissionsIntentionally unsupportedTokens are ignored to keep local tests zero-config.
Real Exchange delivery, spam filtering, transport rulesIntentionally unsupportedNo side effects leave the process.
Calendar, contacts, OneDrive, Teams, non-mail Graph resourcesIntentionally unsupportedThis fake is scoped to Graph mail.
MIME upload sessions and large attachment upload sessionsIntentionally unsupportedUse normal file attachment contentBytes.

Error Shapes

Errors follow the Microsoft Graph JSON shape:

{
  "error": {
    "code": "ErrorItemNotFound",
    "message": "Message not found",
    "innerError": {
      "date": "2026-06-11T00:00:00.000Z",
      "request-id": "req_...",
      "client-request-id": "optional-client-id"
    }
  }
}

Common returned codes:

HTTPGraph codeWhen
400ErrorInvalidRequestMissing required fields, invalid JSON, invalid default-folder deletion.
404ErrorItemNotFoundMissing users, folders, messages, attachments, rules, categories, or subscriptions.
404Request_ResourceNotFoundUnknown route.
405Request_BadRequestUnsupported HTTP method for an implemented route.
500InternalServerErrorUnexpected server errors.
<!-- parlel:testenv:start -->

Configuration — test.env

Copy these into your test.env (used by the bridge sidecar flow). Tokens are Parlel's seeded test credentials — any non-empty value is accepted by the emulator, so you rarely need to change them. Swap in real credentials only when pointing at the live service in prod.env.

OUTLOOK_EMULATOR_HOST=http://parlel-bridge:4620
MICROSOFT_GRAPH_BASE_URL=http://parlel-bridge:4620/v1.0
AZURE_TENANT_ID=parlel
AZURE_CLIENT_ID=parlel
AZURE_CLIENT_SECRET=parlel
<!-- parlel:testenv:end -->