Email send preflight

Send and Save as Outlook draft on an Essentials/Email record check that the mail can go before they do anything. The checks run in two places: as the page renders, and on the click.

Checked as the page renders

While the record is a draft and the approvals allow sending, the page runs two cheap checks:

Check How When it fails
The record is a draft, has at least one To recipient, and every To/Cc entry is an address EmailPreflight.Problems (pure, on the record) Send and Save as Outlook draft are drawn disabled, and the line under them lists every reason
The viewer's mailbox is connected IEaGraphAuth.GetConnection — reads the stored grant, mints no token Connect your mailbox appears first in the row, and Send and Save as Outlook draft are disabled

To and Cc are split on ; and ,. Each entry is either Name <name@example.com> or a bare address; an entry that does not parse is reported by name. An attachment line with no link is a note, not a problem.

When the sign-in appears: only when the mailbox read completes with NotConnected. While the read is pending, or when it answers Undetermined (a fault or a timeout), nothing is blocked and no sign-in is shown; the click checks again. Connect your mailbox goes to the mailbox sign-in (EmailScript.SignInUrl) and returns to the message afterwards.

Checked on the click

A click repeats the render checks against the live record and its approvals, then runs a kernel preflight before the real script is written:

  1. The page writes {message}/Preflight — the same DSL (Essentials/Email/SendScript) and the same plan as the real run, ending in .Preflight() instead of .Send() / .SaveAsDraft() — and runs it on the kernel as the person who clicked.
  2. .Preflight() loads the mail runtime (MailRuntime.Probe(): Microsoft Graph, Kiota, Azure.Core, Markdig), re-checks the record and its recipients, reads every linked attachment, and mints the clicker's delegated mail token. It exports nothing, calls Graph for nothing, and writes nothing but its own Code node and activity.
  3. When the preflight succeeds, the page reads the message again. If it changed during the preflight, the click stops with "Not sent — the message changed while it was being checked …": the approvals and the plan are bound to the version that was checked. Otherwise the page writes {message}/Script and runs Send or Save as Outlook draft.

While this runs the status line reads "Checking that the mail can go … Nothing is sent yet…".

Preflight result What the user sees
Succeeded "Checked. Exporting the attachments and …", then the real run and its outcome
Mailbox not connected ([mailbox-not-connected]) The mailbox sign-in, returning to the message
Mail runtime cannot load ([mail-runtime-unavailable]) "Not sent — the check before sending failed: this server cannot load the mail runtime … this is a deployment fault, not a problem with the message", with the failing line and a link to the preflight's activity
Anything else (unreadable attachment, bad recipient, record no longer a draft, token undetermined) "Not sent — the check before sending failed: ", with a link to the preflight's activity

The [mail-runtime-unavailable] refusal means the deployment cannot load the mail runtime; nothing on the message can fix it.

The real run also probes the runtime first, so a direct run of {message}/Script fails with the same marker before any attachment is exported.

Invariants in the code

Tests: Essentials/Email/Test/EmailPreflightTests.cs, run by the type's Tests area.