Live training: next batches coming soon. Join the waitlist:Waitlists open:Developer Training →Developer →Copilot Studio Training →Copilot Studio →

How to Send Email in SPFx with Microsoft Graph and PnPjs (2026 Update)

Our operations team asked for a small “Notify the warehouse” form on their SharePoint home page: type a few lines, press one button, and the email leaves from the person who pressed it. I reached for the PnPjs sp.utility.sendEmail() snippet I’d used in SharePoint Framework web parts for years.

It doesn’t work anymore. Microsoft retired the SharePoint SendEmail API on 10/31/2025, and today the same call comes back with a 400 error instead of an email. The replacement is Microsoft Graph, which sends from the signed-in user’s own mailbox.

In this tutorial, we’ll build a React web part that can send email in SPFx both ways: Microsoft Graph /me/sendMail through MSGraphClientV3, and the old PnPjs method, so you can see exactly what it returns now. We’ll also fix the “mailbox is either inactive, soft-deleted, or is hosted on-premise” error that guest users hit.

Quick answer: Add { "resource": "Microsoft Graph", "scope": "Mail.Send" } to webApiPermissionRequests in config/package-solution.json and have an admin approve it under SharePoint admin center > Advanced > API access. In code, get this.context.msGraphClientFactory.getClient('3') and call client.api('/me/sendMail').post({ message, saveToSentItems: true }). Graph answers 202 Accepted. Don’t use PnPjs sp.utility.sendEmail(): the SharePoint SendEmail API behind it was retired on 10/31/2025.

Here’s the finished web part after a successful send from the SPGuides site workbench:

SPFx Send Email web part sending a test email with Microsoft Graph sendMail and showing 202 Accepted
Tested withVersion
SharePoint Framework (SPFx)1.23.2 with the Heft toolchain
Node.js22 LTS
React / Fluent UI17.0.1 / @fluentui/react 8
PnPjs@pnp/sp 4.22.0
Graph clientMSGraphClientV3 from @microsoft/sp-http 1.23.2

Which Way Should You Send Email from SPFx in 2026?

There are three realistic options, and only one of them runs inside the web part today. If you’re new to calling Graph from a web part, my user profile web part with Microsoft Graph is a gentler first project.

MethodSends asStatus in 2026When I use it
Microsoft Graph POST /me/sendMail (MSGraphClientV3 or PnPjs @pnp/graph)The signed-in userWorks; needs the Mail.Send permissionAny web part that emails on the user’s behalf
PnPjs sp.utility.sendEmail() (SP.Utilities.Utility.SendEmail)SharePoint (no-reply)Retired 10/31/2025; returns HTTP 400Never in new code; replace it
Power Automate flow (Outlook “Send an email (V2)”)The flow owner or a service accountWorksGuests, users without a mailbox, a fixed sender

Graph also sends to people outside your organization, so the old split of “PnPjs for internal people, Graph for external users” no longer makes sense. When the email must come from a shared or service mailbox instead of the user, read sending email on behalf of another user in SPFx.

What Happened to sp.utility.sendEmail (SP.Utilities.Utility.SendEmail)?

For years, PnPjs wrapped SharePoint’s _api/SP.Utilities.Utility.SendEmail endpoint in one call. It’s still in PnPjs v4 (now marked deprecated), so this code compiles fine. The older SPFx CRUD example with no framework shows how many projects mixed it into list code:

import '@pnp/sp/sputilities';
import { IEmailProperties } from '@pnp/sp/sputilities';

const props: IEmailProperties = {
  To: ['admin@spguides.onmicrosoft.com'],
  Subject: 'SPGDemo PnPjs sendEmail test',
  Body: 'Hello from SPFx',
  AdditionalHeaders: { 'content-type': 'text/html' }
};
await sp.utility.sendEmail(props);   // POST _api/SP.Utilities.Utility.SendEmail

I kept this method in the demo web part on purpose. Selecting PnPjs sendEmail and pressing Send email in the workbench returned this:

PnPjs sendEmail in an SPFx web part returning 400 The SendEmail api has been retired

The exact text is 400: The SendEmail api has been retired. Visit "https://support.microsoft.com/en-us/office/retirement-of-the-sharepoint-sendemail-api-b35bbab1-7d09-455f-8737-c2de63fe0821" for more information.. Nothing is sent and nothing lands in anyone’s inbox. The retirement covers web parts, custom code and Power Automate HTTP calls to the same endpoint; only SharePoint 2013 workflows were left alone.

If your tenant still has old solutions calling it, admins can search the Microsoft Purview audit log for the operation SendEmailApiInvoked. Flows that posted to the same endpoint with an HTTP action should switch to the Outlook connector, as in sending email using the REST API in Power Automate.

Set Up PnPjs v4 in SPFx: spfi and the SPFx Context

A lot of searches land here looking for the current PnPjs setup, because older posts still show sp.setup(), sp-pnp-js or @pnp/sp/presets/all. None of those belong in a 2026 project. Version 4 uses a factory function plus the SPFx behavior, created once in onInit:

import { spfi, SPFI, SPFx } from '@pnp/sp';
import '@pnp/sp/webs';

// inside your web part class
private _sp!: SPFI;

protected async onInit(): Promise<void> {
  await super.onInit();
  this._sp = spfi().using(SPFx(this.context));   // once, never inside a click handler
}

Then import only the parts you call (@pnp/sp/webs, @pnp/sp/lists, @pnp/sp/items and so on). The SPFx list items web part uses the same instance to read a list. If you need SharePoint and Graph in one web part, alias the two behaviors:

import { spfi, SPFx as spSPFx } from '@pnp/sp';
import { graphfi, SPFx as graphSPFx } from '@pnp/graph';

const sp = spfi().using(spSPFx(this.context));
const graph = graphfi().using(graphSPFx(this.context));

With @pnp/graph, sending is one line. It calls the same /me/sendMail endpoint and needs the same Mail.Send approval:

import '@pnp/graph/users';
import '@pnp/graph/mail/messages';

await graph.me.sendMail({
  subject: 'SPGDemo order shipped',
  body: { contentType: 'html', content: 'Order <b>SO-1102</b> shipped today.' },
  toRecipients: [{ emailAddress: { address: 'admin@spguides.onmicrosoft.com' } }]
});

In the build below I use MSGraphClientV3 instead, because it ships with SPFx and adds nothing to the bundle. Pick one; both behave the same.

Build the Send Email Web Part Step by Step

You need a working SPFx 1.23 setup: Node.js 22, Yeoman, the SharePoint generator and Heft. If your machine isn’t ready, follow the SPFx development environment setup with Heft first. I work in Visual Studio Code and run every command in its terminal.

Step 1: Create the project

Create a folder and run the generator with the React template:

md spg-send-email
cd spg-send-email
yo @microsoft/sharepoint --solution-name spg-send-email --component-type webpart --component-name SendEmail --framework react --environment spo

The generator installs the packages for you, which takes a few minutes. Older posts used gulp serve; since SPFx 1.22 the toolchain is Heft, and new projects get npm run start and npm run build scripts. Upgrading an old project? See migrating an SPFx project from gulp to Heft.

Step 2: Install PnPjs and the HTTP package

npm install @pnp/sp@4.22.0 @microsoft/sp-http@1.23.2 --save-exact

@microsoft/sp-http holds the MSGraphClientV3 type. Pin exact versions so every developer builds the same bundle. Then add two folders under src/webparts/sendEmail: models and services.

VS Code showing the SPFx send email project with models, services and components folders and EmailService.ts open

Step 3: Define the message types

Create src/webparts/sendEmail/models/IEmailMessage.ts. One shape serves both send methods:

// What the form collects. One shape for both send methods.
export interface IEmailMessage {
  to: string[];
  subject: string;
  body: string;
}

export type SendMethod = 'pnpjs' | 'graph';

export interface ISendResult {
  ok: boolean;
  text: string;
}

Step 4: Write the email service

Create src/webparts/sendEmail/services/EmailService.ts. It holds both methods and turns errors into readable text:

import { WebPartContext } from '@microsoft/sp-webpart-base';
import { MSGraphClientV3 } from '@microsoft/sp-http';
import { spfi, SPFI, SPFx } from '@pnp/sp';
import '@pnp/sp/webs';
import '@pnp/sp/sputilities';
import { IEmailProperties } from '@pnp/sp/sputilities';
import { IEmailMessage, ISendResult } from '../models/IEmailMessage';

export class EmailService {
  private _sp: SPFI;
  private _context: WebPartContext;

  constructor(context: WebPartContext) {
    this._context = context;
    // One PnPjs instance for the whole web part, created once.
    this._sp = spfi().using(SPFx(context));
  }

  // Method 1: SharePoint's SP.Utilities.Utility.SendEmail through PnPjs.
  public async sendWithPnP(msg: IEmailMessage): Promise<ISendResult> {
    const props: IEmailProperties = {
      To: msg.to,
      Subject: msg.subject,
      Body: msg.body,
      AdditionalHeaders: { 'content-type': 'text/html' }
    };
    try {
      await this._sp.utility.sendEmail(props);
      return { ok: true, text: 'SharePoint accepted the request (SP.Utilities.Utility.SendEmail).' };
    } catch (e) {
      return { ok: false, text: await this._spError(e) };
    }
  }

  // Method 2: Microsoft Graph POST /me/sendMail (needs the Mail.Send delegated permission).
  public async sendWithGraph(msg: IEmailMessage): Promise<ISendResult> {
    const mail = {
      message: {
        subject: msg.subject,
        body: { contentType: 'HTML', content: msg.body },
        toRecipients: msg.to.map(address => ({ emailAddress: { address } }))
      },
      saveToSentItems: true
    };
    try {
      const client: MSGraphClientV3 = await this._context.msGraphClientFactory.getClient('3');
      await client.api('/me/sendMail').version('v1.0').post(mail);
      return { ok: true, text: 'Microsoft Graph returned 202 Accepted. Check your Sent Items folder.' };
    } catch (e) {
      const err = e as { statusCode?: number; code?: string; message?: string };
      return { ok: false, text: `${err.statusCode ?? ''} ${err.code ?? ''}: ${err.message ?? String(e)}`.trim() };
    }
  }

  // Turns a PnPjs HttpRequestError into SharePoint's own message text.
  private async _spError(e: unknown): Promise<string> {
    const err = e as { isHttpRequestError?: boolean; response?: Response; status?: number; message?: string };
    if (err.isHttpRequestError && err.response) {
      try {
        const json = await err.response.clone().json();
        const m = json?.['odata.error']?.message?.value ?? json?.error?.message?.value ?? json?.error?.message;
        if (m) { return `${err.status}: ${m}`; }
      } catch {
        // the body was not JSON; fall back to the PnPjs message
      }
    }
    return err.message ?? String(e);
  }
}
  • sendWithGraph builds the Graph message object (subject, HTML body, one toRecipients entry per address) and posts it to /me/sendMail. Graph returns 202 with no body, so reaching the next line means success.
  • saveToSentItems: true is the default; I set it so you can see it. Use false for automatic notices you don’t want cluttering Sent Items.
  • sendWithPnP is the retired call, kept only to show the error. The _spError helper reads SharePoint’s own message out of the PnPjs HttpRequestError.
  • Graph failures come back as a GraphError with statusCode, code and message, which the web part shows as is.

Step 5: Build the form component

Replace src/webparts/sendEmail/components/ISendEmailProps.ts:

import { EmailService } from '../services/EmailService';

export interface ISendEmailProps {
  service: EmailService;
  currentUserEmail: string;
  subjectPrefix: string;
}

Then replace src/webparts/sendEmail/components/SendEmail.tsx. It’s a Fluent UI form with To, Subject and Body fields, a Fluent UI ChoiceGroup for the method, a spinner and a MessageBar:

import * as React from 'react';
import {
  TextField, PrimaryButton, ChoiceGroup, IChoiceGroupOption,
  MessageBar, MessageBarType, Spinner, SpinnerSize, Stack
} from '@fluentui/react';
import styles from './SendEmail.module.scss';
import { ISendEmailProps } from './ISendEmailProps';
import { SendMethod, ISendResult } from '../models/IEmailMessage';

const methods: IChoiceGroupOption[] = [
  { key: 'graph', text: 'Microsoft Graph (/me/sendMail)' },
  { key: 'pnpjs', text: 'PnPjs sendEmail (SharePoint SendEmail API)' }
];

const SendEmail: React.FC<ISendEmailProps> = ({ service, currentUserEmail, subjectPrefix }) => {
  const [to, setTo] = React.useState<string>(currentUserEmail);
  const [subject, setSubject] = React.useState<string>(`${subjectPrefix} Test email from SPFx`);
  const [body, setBody] = React.useState<string>('Hello,<br/>This email was sent from an SPFx web part.');
  const [method, setMethod] = React.useState<SendMethod>('graph');
  const [busy, setBusy] = React.useState<boolean>(false);
  const [result, setResult] = React.useState<ISendResult | undefined>(undefined);

  const recipients = to.split(/[;,]/).map(s => s.trim()).filter(s => s.length > 0);
  const canSend = recipients.length > 0 && subject.trim().length > 0 && !busy;

  const send = async (): Promise<void> => {
    setBusy(true);
    setResult(undefined);
    const msg = { to: recipients, subject: subject.trim(), body };
    const r = method === 'pnpjs' ? await service.sendWithPnP(msg) : await service.sendWithGraph(msg);
    setResult(r);
    setBusy(false);
  };

  return (
    <section className={styles.sendEmail}>
      <h2 className={styles.title}>Send an email</h2>
      <Stack tokens={{ childrenGap: 12 }}>
        <TextField label="To" required value={to} onChange={(_, v) => setTo(v || '')}
          description="Separate several addresses with a semicolon." />
        <TextField label="Subject" required value={subject} onChange={(_, v) => setSubject(v || '')} />
        <TextField label="Body (HTML allowed)" multiline rows={5} value={body} onChange={(_, v) => setBody(v || '')} />
        <ChoiceGroup label="Send with" options={methods} selectedKey={method}
          onChange={(_, o) => o && setMethod(o.key as SendMethod)} />
        <Stack horizontal tokens={{ childrenGap: 12 }} verticalAlign="center">
          <PrimaryButton text="Send email" iconProps={{ iconName: 'Send' }} disabled={!canSend} onClick={send} />
          {busy && <Spinner size={SpinnerSize.small} label="Sending..." labelPosition="right" />}
        </Stack>
        {result && (
          <MessageBar messageBarType={result.ok ? MessageBarType.success : MessageBarType.error} isMultiline>
            {result.text}
          </MessageBar>
        )}
      </Stack>
    </section>
  );
};

export default SendEmail;

The To box starts with the signed-in user’s address, and several addresses can be separated with a semicolon. The Send button stays disabled until there’s a recipient and a subject; for richer rules, see SPFx form validation. The styles in SendEmail.module.scss are small:

.sendEmail {
  max-width: 640px;
  padding: 16px;
  border: 1px solid "[theme:neutralLight, default: #edebe9]";
  border-radius: 4px;
  color: "[theme:bodyText, default: #323130]";
}

.title {
  margin: 0 0 8px;
  font-size: 20px;
  font-weight: 600;
}

Step 6: Wire up the web part

Replace src/webparts/sendEmail/SendEmailWebPart.ts. It creates the service once in onInit and passes it to React:

import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import { type IPropertyPaneConfiguration, PropertyPaneTextField } from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';

import * as strings from 'SendEmailWebPartStrings';
import SendEmail from './components/SendEmail';
import { ISendEmailProps } from './components/ISendEmailProps';
import { EmailService } from './services/EmailService';

export interface ISendEmailWebPartProps {
  subjectPrefix: string;
}

export default class SendEmailWebPart extends BaseClientSideWebPart<ISendEmailWebPartProps> {
  private _service!: EmailService;

  protected async onInit(): Promise<void> {
    await super.onInit();
    this._service = new EmailService(this.context);
  }

  public render(): void {
    const element: React.ReactElement<ISendEmailProps> = React.createElement(SendEmail, {
      service: this._service,
      currentUserEmail: this.context.pageContext.user.email,
      subjectPrefix: this.properties.subjectPrefix || 'SPGDemo'
    });
    ReactDom.render(element, this.domElement);
  }

  protected onDispose(): void {
    ReactDom.unmountComponentAtNode(this.domElement);
  }

  protected get dataVersion(): Version {
    return Version.parse('1.0');
  }

  protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [{
        header: { description: strings.PropertyPaneDescription },
        groups: [{
          groupName: strings.BasicGroupName,
          groupFields: [
            PropertyPaneTextField('subjectPrefix', { label: strings.SubjectPrefixFieldLabel })
          ]
        }]
      }]
    };
  }
}

The property pane has one field, Subject prefix, which I default to SPGDemo so test emails are easy to find and filter. The SPFx property pane controls guide shows other field types. Update loc/mystrings.d.ts:

declare interface ISendEmailWebPartStrings {
  PropertyPaneDescription: string;
  BasicGroupName: string;
  SubjectPrefixFieldLabel: string;
}

declare module 'SendEmailWebPartStrings' {
  const strings: ISendEmailWebPartStrings;
  export = strings;
}

And loc/en-us.js:

define([], function() {
  return {
    "PropertyPaneDescription": "Send an email from SharePoint with Microsoft Graph or PnPjs.",
    "BasicGroupName": "Settings",
    "SubjectPrefixFieldLabel": "Subject prefix"
  }
});

Last, give the web part a friendly toolbox name and the default prefix in SendEmailWebPart.manifest.json (only preconfiguredEntries changes):

"preconfiguredEntries": [{
  "groupId": "5c03119e-3074-46fd-976b-c60198311f70",
  "group": { "default": "Advanced" },
  "title": { "default": "Send Email" },
  "description": { "default": "Send an email with Microsoft Graph or PnPjs" },
  "officeFabricIconFontName": "Mail",
  "properties": {
    "subjectPrefix": "SPGDemo"
  }
}]

Request and Approve the Mail.Send Permission

Graph won’t let the web part send mail until the tenant grants Mail.Send (delegated) to SharePoint’s shared service principal. Declare the request in config/package-solution.json, inside solution:

"webApiPermissionRequests": [
  {
    "resource": "Microsoft Graph",
    "scope": "Mail.Send"
  }
]
package-solution.json in VS Code with webApiPermissionRequests for Microsoft Graph Mail.Send

When the package is added to the app catalog, the request shows up under SharePoint admin center > Advanced > API access, where a SharePoint or Global admin approves it. The grant is tenant-wide: every SPFx solution in the tenant can then use Mail.Send. The SharePoint app catalog guide covers the catalog side.

In our tenant, Mail.Send was already approved for an earlier solution, so the workbench could send right away. If yours isn’t approved yet, the Graph call fails with a 403 until an admin approves it.

Step 7: Run it in the SPGuides workbench

Point the local server at your own site’s workbench in config/serve.json:

{
  "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/spfx-serve.schema.json",
  "port": 4321,
  "https": true,
  "initialPage": "https://{tenantDomain}/sites/SPGuides/_layouts/workbench.aspx"
}

Heft replaces {tenantDomain} with the SPFX_SERVE_TENANT_DOMAIN environment variable (SPFx environment variables explains how to make it permanent). Then start the server:

$env:SPFX_SERVE_TENANT_DOMAIN = "spguides.sharepoint.com"
npm run start
npm run start in the VS Code terminal serving the SPFx send email web part with the debug query string

Heft builds the project, serves it from https://localhost:4321 and opens the workbench. If the page asks, select Load debug scripts so it picks up your local bundle. Then select the + button, search for Send Email and select it:

SharePoint workbench toolbox search showing the Send Email SPFx web part

Heads-up: Microsoft retires the hosted workbench on 12/01/2026. After that, keep npm run start running, open a real page on your site, add ?debugManifestsFile=https%3A%2F%2Flocalhost%3A4321%2Ftemp%2Fbuild%2Fmanifests.js&debug=true&noredir=true to its URL and select Load debug scripts.

Test Both Methods

I sent every test email to my own mailbox with the SPGDemo prefix. With Microsoft Graph (/me/sendMail) selected, the web part showed Microsoft Graph returned 202 Accepted. Check your Sent Items folder. within a second, and the message arrived in the inbox and in Sent Items, sent from my own account.

Then I switched to PnPjs sendEmail and pressed Send again. That’s the retired-API error shown earlier: same form, same recipient, no email.

Remember that 202 means Exchange accepted the message, not that it was delivered. Delivery still goes through Exchange Online transport rules and limits, the same pipeline that Power Automate list notification emails use.

The POST /me/sendMail Request, Explained

Under the fluent client.api('/me/sendMail').post(mail) call, the web part sends this HTTP request. It’s handy for testing the same body in Graph Explorer, or for calling Graph from other SPFx web parts that use Graph:

POST https://graph.microsoft.com/v1.0/me/sendMail
Content-Type: application/json

{
  "message": {
    "subject": "SPGDemo Test email from SPFx",
    "body": { "contentType": "HTML", "content": "Hello,<br/>This email was sent from an SPFx web part." },
    "toRecipients": [ { "emailAddress": { "address": "admin@spguides.onmicrosoft.com" } } ]
  },
  "saveToSentItems": true
}
  • message.body.contentType is Text or HTML.
  • toRecipients, ccRecipients and bccRecipients are arrays of { emailAddress: { address } }; external addresses are fine.
  • attachments take #microsoft.graph.fileAttachment objects with base64 contentBytes in the same call.
  • /me always means the signed-in user. To send from a shared mailbox, call /users/{id}/sendMail with the right Exchange permissions.

Fix “The mailbox is either inactive, soft-deleted, or is hosted on-premise.”

This is the error people hit after a Graph web part goes live. Everything works for employees, then a guest or an unlicensed account presses Send and Graph answers with:

{
  "error": {
    "code": "MailboxNotEnabledForRESTAPI",
    "message": "The mailbox is either inactive, soft-deleted, or is hosted on-premise."
  }
}

/me/sendMail sends from the signed-in user’s Exchange Online mailbox. If that user has no usable mailbox in your tenant, Graph has nothing to send from. Typical causes:

  • Guest users. A guest can open your SharePoint site but has no mailbox in your tenant. Graph can send to guests; it just can’t send as them.
  • No Exchange Online license. Accounts without a plan that includes Exchange Online have no mailbox, and a mailbox that’s still being provisioned behaves the same for a while.
  • Soft-deleted or inactive mailbox. A deleted user, or a mailbox kept only for retention, can’t send.
  • On-premises mailbox. Graph only reaches Exchange Online. Access to on-premises mailboxes in Exchange hybrid setups ended in July 2023.
  • Shared mailbox as /me. Nobody signs in as a shared mailbox; send from it with /users/{id}/sendMail instead.

Fixes, from simplest to most robust: license the user with Exchange Online; or have the web part save the request to a list and let a Power Automate flow send it from a service account, much like sending email from Power Apps; or call an Azure Function that uses the application Mail.Send permission, scoped to one sender mailbox.

I didn’t reproduce this error in our tenant, because it needs an unlicensed or guest account; the cause list above comes from Microsoft’s Graph documentation and Q&A answers. For a fixed sender address, the send-on-behalf post linked earlier is the next step.

Common Errors and How to Fix Them

  • 400: The SendEmail api has been retired. Visit “https://support.microsoft.com/en-us/office/retirement-of-the-sharepoint-sendemail-api-b35bbab1-7d09-455f-8737-c2de63fe0821” for more information. Your code still calls PnPjs sp.utility.sendEmail() or _api/SP.Utilities.Utility.SendEmail. Switch to Graph /me/sendMail.
  • 403 ErrorAccessDenied (Access is denied). Mail.Send isn’t approved yet, or it was approved for a different resource. Check API access in the SharePoint admin center.
  • HTTP 400 when approving the request in API access. Microsoft’s documented workaround: set "resource": "Microsoft.Azure.AgregatorService" instead of "Microsoft Graph", reject the old request and update the package.
  • Send Email isn’t in the workbench toolbox. npm run start stopped or is still compiling. Wait for the debug query string line in the terminal, then refresh.
  • 202 but no email arrives. Check the recipient address and the sender’s Sent Items, then the Exchange message trace; transport rules can still block delivery after Graph accepts the message.

Package the Web Part

When the web part works, stop the server with Ctrl+C and build the production package:

npm run build

The script runs heft test --clean --production and then heft package-solution --production. It took a little over four minutes on the first run (263 seconds) here and wrote sharepoint/solution/spg-send-email.sppkg:

npm run build in the VS Code terminal creating the spg-send-email.sppkg SPFx package

Upload the .sppkg to the app catalog when you’re ready; that’s also when the Mail.Send request appears for approval. If you’d like this web part and many more as ready-to-run projects, they’re on the Power Platform and SPFx solutions page.

More SPFx builds that pair well with this one:

Frequently Asked Questions

How do I send email from an SPFx web part?

Request the Microsoft Graph Mail.Send permission in package-solution.json, have an admin approve it in API access, then call client.api(‘/me/sendMail’).post({ message }) with MSGraphClientV3 from this.context.msGraphClientFactory.getClient(‘3’). Graph returns 202 Accepted.

Does PnPjs sp.utility.sendEmail still work?

No. It calls SharePoint’s SendEmail API, which Microsoft retired on 10/31/2025 (retirement notice). In our test it returned HTTP 400 with “The SendEmail api has been retired.” Use Microsoft Graph instead.

What permission does POST /me/sendMail need?

The delegated Mail.Send permission; there’s no lower-privileged option. The Microsoft Graph sendMail reference lists the request body and the 202 Accepted response.

How do I set up PnPjs v4 in SPFx?

Import spfi and SPFx from @pnp/sp, then in onInit (after await super.onInit()) create one instance with spfi().using(SPFx(this.context)). For Graph, use graphfi().using(SPFx(this.context)) from @pnp/graph. Don’t use sp.setup() or @pnp/sp/presets/all.

Why do guest users get “The mailbox is either inactive, soft-deleted, or is hosted on-premise”?

Because /me/sendMail sends from the signed-in user’s own Exchange Online mailbox, and a guest has none in your tenant. Send through a Power Automate flow or an app-only service instead, or license the user.

3 thoughts on “How to Send Email in SPFx with Microsoft Graph and PnPjs (2026 Update)”

  1. after downloading the solution, i m not able to open.
    and I m getting error on isRequired type…
    Property ‘isRequired’ does not exist on type ‘IntrinsicAttributes & IntrinsicClassAttributes & Readonly & Readonly’. Did you mean ‘required’?ts
    this._getPeoplePickerItems(Items, “CcEmail”)}
    showHiddenInUI={false}
    principalTypes={[PrincipalType.User]}
    resolveDelay={1000}
    ensureUser={true}
    />

Leave a Comment