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?

High-level steps

  1. Add the required delegated Graph permission (Mail.Send) to your SPFx solution.

  2. Package and request admin consent in SharePoint Admin (tenant admin must approve the permission).

  3. In your web part, get an MSGraphClient instance and call POST /me/sendMail with the message payload.

  4. 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"
      }
    ]
  }
}

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

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

Admin consent flow & deployment notes

  1. Add webApiPermissionRequests to package-solution.json as shown.

  2. Package solution: gulp bundle --ship and gulp package-solution --ship.

  3. Upload .sppkg to App Catalog and deploy.

  4. Tenant admin goes to SharePoint Admin → API access (or Azure AD Enterprise Apps → Consent) and Grant the requested Microsoft Graph permission (Mail.Send).

  5. After granted, your SPFx client can call the Graph endpoint as the signed-in user.

Common mistakes

Troubleshooting

Security considerations

Advanced notes

Using GraphHttpClient vs MSGraphClient

Sending templated HTML

Logging and monitoring

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:

Recap / Checklist