Skip to main content
The m365_mail kind collects one folder of one Microsoft 365 mailbox through Microsoft Graph, for example a shared monitoring mailbox that receives alerts, press releases or newsletters, through the first-party plugin plugins/m365-mail (step 3). Read the Connector Instances overview first: creation, credentials, health and the change feed work the same way for every kind.

What it collects

  • New mail only. An instance collects mail received from its creation, or from backfill_since when set (at most 7 days before its first poll). Mail already in the folder before that point is not collected.
  • One Record per mail. The Record Key is the mail’s internetMessageId. When a mail has none, the key is graph: followed by its immutable Graph id.
  • Parts of each Record Version: Each attachment Part keeps the attachment’s media type and its verified SHA-256 checksum. Its connector.m365_mail.attachment extension records the file name, size, inline flag and type (file, or item for an attached mail or event stored as MIME). Attachment contents are stored, not extracted: search covers the subject and the body only.
  • Headers. The connector.m365_mail extension (schema version 1) holds:
    • from, sender, to, cc;
    • subject, sent_at, received_at;
    • conversation_id, internet_message_id, graph_id, folder;
    • to_count and cc_count: the full recipient counts. to and cc keep at most the first 100 recipients each;
    • attachments_skipped: attachments that were not stored, each with a reason. The reasons are too_large (over 25 MB, announced or found while downloading), reference_attachment (a link to a cloud file, not a file) and empty.
  • What does not create a new Version. Read/unread changes and flags. A genuine change to a mail’s content or attachments becomes a correction.
  • What is never withdrawn. Moving or deleting a mail in the mailbox leaves its Record untouched. Withdraw it through the ingestion API if needed.

  1. Register an application in Microsoft Entra ID

A tenant administrator performs these steps once per organization.
  1. In the Microsoft Entra admin center, open App registrations > New registration. Choose a descriptive name, single tenant. No redirect URI is needed.
  2. Note the Directory (tenant) ID and the Application (client) ID.
  3. Under Certificates & secrets, add a credential:
    • Certificate (recommended). Upload the public certificate. Keep the certificate and its RSA private key in PEM form for step 4.
    • Client secret. Copy the secret value when it is shown; it cannot be displayed again.
    Either way, note the expiry date.

  1. Restrict the application to the monitored mailboxes

Quivr needs to read mail, including bodies and attachments. The Graph permission for that is Mail.Read, which lets an application read every mailbox in the tenant unless it is scoped. Scope it to the monitored mailboxes with one of these two methods. Recommended: RBAC for Applications in Exchange Online. Run these in Exchange Online PowerShell as an Exchange administrator:
With this method, do not grant Mail.Read to the application in Entra ID. Grants from Entra ID and from Exchange RBAC add up, so a tenant-wide Entra grant would cancel the scope. Alternative: an application access policy. This older mechanism, which Microsoft is replacing with RBAC for Applications, works like this:
  1. Grant the Mail.Read application permission under API permissions.
  2. Grant admin consent.
  3. Restrict it to a mail-enabled security group containing the monitored mailboxes:
Exchange caches permission changes, so they can take from 30 minutes to 2 hours to apply. Until then Quivr may report mailbox_access_denied.

  1. Deployment settings

The kind comes from the connector.m365_mail plugin: build plugins/m365-mail (go build), run it beside the worker and pin it in QUIVR_CONFIG (run a connector plugin). make dev, make verify and the Railway image pin it already. By default it talks to the global Microsoft cloud; the pin configuration overrides both endpoints, for example for a national cloud:
An API caller cannot choose these endpoints, so a deposited secret is only ever sent to them. The plugin needs outbound HTTPS access to both and to storage: it uploads each attachment to a presigned URL the core issues, pinned to its size, SHA-256 and media type, and the core reads the bytes back before accepting the mail. The plugin never holds storage credentials. Upgrading from the built-in kind. A deployment that still sets the engine’s m365 block refuses to start: move its endpoints into the pin configuration. Instances, checkpoints, credentials and health carry over, with no duplicate and no second download. Unpinned, instances report unsupported_connector_kind.

  1. Create the instance

For a certificate, use certificate_pem (the -----BEGIN CERTIFICATE----- block) and private_key_pem (an RSA key, PKCS #8 or PKCS #1). Quivr signs a short-lived client assertion with it and never sends the key.

  1. Rotate the secret or certificate

  1. In Entra ID, add a new secret or certificate while the old one is still valid.
  2. Deposit it:
  3. Once health is active again, delete the old credential in Entra ID.
The plugin caches access tokens in its memory only, keyed by the credential, so the next run already uses the new secret.

Health codes and troubleshooting

GET /v0/connectors/{connector_id} reports health. Access problems set access_error until a later successful poll. Transient problems show only in last_error. Other behaviour:
  • Retry-After. Short delays (up to 10 seconds) are waited within the run.
  • Delta resync. When Graph invalidates the folder’s delta state (410 Gone, syncStateNotFound), the connector re-reads the folder over the collection window, bounded to the last 7 days, without creating duplicates.
  • What is logged. Secrets, tokens, addresses and subjects are never logged; logs carry the connector ID and codes only.

Limits

  • 25 MB per stored attachment. Larger attachments are listed as too_large.
  • One folder per instance, without subfolders. Create one instance per folder, each with its own Source Namespace.
  • A run reads up to 10 pages of 10 mails, stores up to 200 MB of attachments and starts no new page after 2 minutes. A larger backlog continues on the next run.
  • Attachment text (PDF, Office documents) is not extracted yet.

Optional check against a real tenant

make verify uses a local fake of Graph (scripts/fake_graph.py) and never contacts Microsoft. To check a real test tenant by hand, register an application scoped to a test mailbox, run make dev with the plugin pinned to the public cloud, create an instance, send a mail with an attachment, and follow GET /v0/changes?corpus_id=… until the Record and its attachment Blob appear. Keep the tenant ID, mailbox and credential out of the repository.