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/sendMail
to send mail as the currently authenticated user.
In SPFx you can call Graph with the built-in MSGraphClient
(or AadHttpClient
/ 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 MSGraphClient
instance and call POST /me/sendMail
with 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 an MSGraphClient
already authenticated as the current user.
If youβre writing in a Web Part class (not a React separate component), this.context.msGraphClientFactory
is 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
β requires Mail.Send
delegated 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.Send
on Microsoft Graph (app-only). This requires admin consent and tenant-wide permissions (very powerful). Then you call /users/{userId}/sendMail
with 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 webApiPermissionRequests
to package-solution.json
as shown.
Package solution: gulp bundle --ship
and gulp package-solution --ship
.
Upload .sppkg
to 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/sendMail
will fail if the account cannot send mail.
Troubleshooting
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.Send
and 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
MSGraphClient
is a convenience wrapper around Graph and provides typed helpers. Preferred for Graph calls when available.
AadHttpClient
/GraphHttpClient
can be used when you need lower-level control. For me/sendMail
, MSGraphClient
is the simplest.
Sending templated HTML
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.Send
to webApiPermissionRequests
in package-solution.json
.
Package & deploy solution to App Catalog.
Tenant admin grants consent in SharePoint Admin β API access.
Use this.context.msGraphClientFactory.getClient()
and call POST /me/sendMail
.
Use saveToSentItems: true
to save a copy in Sent Items.
Add error handling and telemetry.