This article explains how to send an email as the logged-in user from an SPFx web part using Microsoft Graph. It covers permission configuration (package-solution), admin consent, code examples (TypeScript + SPFx MSGraphClient), attachments, troubleshooting, and alternatives (application permissions). Ready-to-use code snippets are included.
Why use Microsoft Graph from SPFx?
Graph is the unified API for Microsoft 365 — it exposes
me/sendMailto send mail as the currently authenticated user.In SPFx you can call Graph with the built-in
MSGraphClient(orAadHttpClient/GraphHttpClient), which handles auth as the current user so you don’t need to manage tokens client-side.Typical use case: web part sends notification, confirmation, or formatted email using the identity of the user interacting with the page.
High-level steps
Add the required delegated Graph permission (
Mail.Send) to your SPFx solution.Package and request admin consent in SharePoint Admin (tenant admin must approve the permission).
In your web part, get an
MSGraphClientinstance and callPOST /me/sendMailwith the message payload.Handle success and errors, and optionally include attachments and
saveToSentItems.
Permission setup (package-solution.json)
Add a webApiPermissionRequests entry to config/package-solution.json so your solution requests delegated permission to send mail:
{
"solution": {
"name": "send-mail-solution",
"id": "00000000-0000-0000-0000-000000000000",
"version": "1.0.0.0",
"webApiPermissionRequests": [
{
"resource": "Microsoft Graph",
"scope": "Mail.Send"
}
]
}
}
After you deploy the package to the App Catalog, a tenant admin must go to the SharePoint admin center → API access and grant the requested permission.
The permission requested above is delegated (mail is sent as the signed-in user). The tenant admin consent is required only once per tenant for that permission request.
Using MSGraphClient in SPFx (TypeScript example)
Below is a simple React + SPFx example that sends an email using /me/sendMail. This assumes you're in a web part and have access to this.context.msGraphClientFactory.
// imports
import * as React from 'react';
import { MSGraphClient } from '@microsoft/sp-http';
import { PrimaryButton, TextField } from '@fluentui/react';
// Example component props
interface ISendMailProps {
context: any; // the web part context
}
export const SendMailComponent: React.FC<ISendMailProps> = ({ context }) => {
const [to, setTo] = React.useState('');
const [subject, setSubject] = React.useState('');
const [body, setBody] = React.useState('');
const [loading, setLoading] = React.useState(false);
const [status, setStatus] = React.useState<string | null>(null);
const sendMail = async (): Promise<void> => {
setLoading(true);
setStatus(null);
try {
const client: MSGraphClient = await context.msGraphClientFactory.getClient();
// Build message payload
const message = {
subject: subject || '(no subject)',
body: {
contentType: 'HTML',
content: body || ''
},
toRecipients: to.split(';').map((addr: string) => ({ emailAddress: { address: addr.trim() } }))
};
// POST /me/sendMail
await client.api('/me/sendMail').post({
message,
saveToSentItems: true
});
setStatus('Email sent successfully.');
} catch (error) {
console.error('sendMail error', error);
// Example error handling
if (error && error.status === 403) {
setStatus('Permission denied — Mail.Send may not be consented for this tenant.');
} else {
setStatus(`Failed to send email: ${error?.message || JSON.stringify(error)}`);
}
} finally {
setLoading(false);
}
};
return (
<div>
<TextField label="To (separate multiple with ; )" value={to} onChange={(_, v) => setTo(v || '')} />
<TextField label="Subject" value={subject} onChange={(_, v) => setSubject(v || '')} />
<TextField label="Body (HTML allowed)" multiline value={body} onChange={(_, v) => setBody(v || '')} />
<PrimaryButton text={loading ? 'Sending...' : 'Send email'} onClick={sendMail} disabled={loading} />
{status && <div style={{ marginTop: 8 }}>{status}</div>}
</div>
);
};
Notes
Use
context.msGraphClientFactory.getClient()to get anMSGraphClientalready authenticated as the current user.If you’re writing in a Web Part class (not a React separate component),
this.context.msGraphClientFactoryis available from the web part's context.
Message payload details
Example minimal Graph payload:
{
"message": {
"subject": "Hello from SPFx",
"body": {
"contentType": "HTML",
"content": "<p>Hi there — this email was sent from an SPFx web part.</p>"
},
"toRecipients": [
{
"emailAddress": {
"address": "[email protected]"
}
}
]
},
"saveToSentItems": true
}
Attachments
To include attachments, the message object supports attachments. For small attachments (<= 3 MB) you can embed fileAttachment objects with contentBytes (base64-encoded) and name. Example:
"attachments": [
{
"@odata.type": "#microsoft.graph.fileAttachment",
"name": "hello.txt",
"contentBytes": "aGVsbG8gd29ybGQ=",
"contentType": "text/plain"
}
]
For larger attachments, use the Outlook createUploadSession pattern through Graph (more complex, beyond this tutorial).
Sending as the user vs sending as another address
Send as current user: use
POST /me/sendMail— requiresMail.Senddelegated permission and the user in the current session. This is the approach we used above.Send as another user: If you need to send as a different mailbox (
/users/{id | userPrincipalName}/sendMail) you still need the appropriate delegated permission and the signed-in user must have permission to send on behalf of that mailbox (or use application permissions — more below).Send as service (no user): use application permission
Mail.Sendon Microsoft Graph (app-only). This requires admin consent and tenant-wide permissions (very powerful). Then you call/users/{userId}/sendMailwith a client credential flow (server-side). This is not supported directly in a client-side SPFx web part (because you cannot securely store client secrets in the browser).
Admin consent flow & deployment notes
Add
webApiPermissionRequeststopackage-solution.jsonas shown.Package solution:
gulp bundle --shipandgulp package-solution --ship.Upload
.sppkgto App Catalog and deploy.Tenant admin goes to SharePoint Admin → API access (or Azure AD Enterprise Apps → Consent) and Grant the requested Microsoft Graph permission (Mail.Send).
After granted, your SPFx client can call the Graph endpoint as the signed-in user.
Common mistakes
Forgetting to deploy the updated package to the App Catalog (the permission request is added in the package).
Not waiting for admin to approve the requested permission.
Using an account without mailbox (e.g., some system accounts) —
me/sendMailwill fail if the account cannot send mail.
Troubleshooting
403 / AccessDenied / Insufficient privileges
Check SharePoint Admin → API access and ensure
Mail.Sendis granted.Ensure the permission requested is delegated (
Mail.Send), not application, if you intend to send as the current user.
401 Unauthorized
Make sure you used
msGraphClientFactory.getClient()and not a raw fetch without a token.The mailbox couldn't be foundorUser does not have a mailboxThe signed-in user must have an Exchange mailbox.
CORS
Typically not an issue when using
MSGraphClientbecause the library handles the authentication and headers.
Message not in Sent Items
Use
saveToSentItems: true. If still missing, check mailbox settings (some tenants can override).
Security considerations
Do not store or hard-code access tokens or client secrets in client-side code.
Follow the least privilege principle: request only
Mail.Sendand not broader scopes.If you require app-only access (application permission) to send on behalf of many users or service accounts, implement server-side code to hold client credentials and perform the Graph calls.
Advanced notes
Using GraphHttpClient vs MSGraphClient
MSGraphClientis a convenience wrapper around Graph and provides typed helpers. Preferred for Graph calls when available.AadHttpClient/GraphHttpClientcan be used when you need lower-level control. Forme/sendMail,MSGraphClientis the simplest.
Sending templated HTML
Sanitize or validate any user-supplied HTML to reduce potential injection risks.
Use inline CSS sparingly; Outlook rendering can be inconsistent.
Logging and monitoring
Log errors to telemetry (Application Insights) from your web part so you can debug send failures in production.
If using app-only flows, consider using Azure AD sign-in logs and Graph usage logs for auditing.
Example: send mail with attachment (small file) — TypeScript snippet
const sendMailWithAttachment = async (client: MSGraphClient) => {
const attachmentContent = btoa('Hello world from SPFx'); // base64
const message = {
subject: 'SPFx mail with attachment',
body: { contentType: 'Text', content: 'Please find attachment.' },
toRecipients: [{ emailAddress: { address: '[email protected]' }}],
attachments: [{
'@odata.type': '#microsoft.graph.fileAttachment',
name: 'hello.txt',
contentBytes: attachmentContent,
contentType: 'text/plain'
}]
};
await client.api('/me/sendMail').post({ message, saveToSentItems: true });
};
When to use server-side (application) approach instead
If you must send mail on behalf of users without them signing in (e.g., scheduled notifications from a backend), the server-side application permission route is appropriate. That requires:
Registering an Azure AD app with application permission
Mail.Send.Admin consent by tenant admin.
A secure backend that performs the OAuth2 client credentials flow and calls
POST /users/{userId}/sendMail.Do not put client secrets in SPFx (client-side code).
Recap / Checklist
Add
Mail.SendtowebApiPermissionRequestsinpackage-solution.json.Package & deploy solution to App Catalog.
Tenant admin grants consent in SharePoint Admin → API access.
Use
this.context.msGraphClientFactory.getClient()and callPOST /me/sendMail.Use
saveToSentItems: trueto save a copy in Sent Items.Add error handling and telemetry.

Join the conversation! Your thoughts help the community grow.