Install
openclaw skills install @felox63/m365-unifiedUnified Microsoft 365 skill for OpenClaw with modular features for Exchange Online (Email), SharePoint, OneDrive, and Planner. Supports webhooks for real-time notifications.
openclaw skills install @felox63/m365-unifiedVersion: 1.0.0
Author: OpenClaw Community
License: MIT
Repository: https://github.com/openclaw/m365-unified-skill
Unified Microsoft 365 skill for OpenClaw providing modular access to Microsoft Graph API services. Features include Exchange Online (Email), SharePoint, OneDrive, and Planner integration with optional webhook support for real-time notifications instead of polling.
| Module | Description | Required Permissions |
|---|---|---|
| Send, receive, move emails, manage folders, handle attachments | Mail.Read, Mail.ReadWrite, Mail.Send | |
| SharePoint | Upload/download files, manage document libraries | Sites.ReadWrite.All, Files.ReadWrite.All |
| OneDrive | Personal file storage, attachment backup | Files.ReadWrite.All |
| Planner | Task management, create tasks from emails | Tasks.ReadWrite, Group.Read.All |
m365-unified/
├── src/
│ ├── index.js # Main entry point (modular client)
│ ├── auth/
│ │ └── graph-client.js # OAuth2 authentication & token management
│ ├── email/
│ │ ├── mail.js # Send, receive, search emails
│ │ ├── folders.js # Folder/mailbox management
│ │ └── attachments.js # Attachment download & handling
│ ├── sharepoint/
│ │ └── files.js # SharePoint file operations
│ ├── onedrive/
│ │ └── files.js # OneDrive file operations
│ ├── planner/
│ │ ├── tasks.js # Task CRUD operations
│ │ └── plans.js # Plan & bucket management
│ └── webhooks/
│ └── subscriptions.js # Webhook subscription lifecycle
├── scripts/
│ ├── setup-wizard.js # Interactive setup & configuration
│ ├── test-connection.js # Test authentication & connectivity
│ ├── test-email.js # Test email features
│ ├── test-sharepoint.js # Test SharePoint features
│ ├── test-onedrive.js # Test OneDrive features
│ ├── test-planner.js # Test Planner features
│ ├── manage-webhooks.js # Create/list/renew/delete webhooks
│ ├── webhook-handler.js # Express server for webhook notifications
│ └── process-invoice-email.js # Example: Invoice processing workflow
├── docs/
│ ├── webhooks.md # Webhook setup & troubleshooting
│ └── SHARED-MAILBOXES.md # Shared mailbox configuration
├── config/
│ └── template.env # Environment template (placeholders only)
├── package.json
└── SKILL.md # This file
cd m365-unified
npm install
npm run setup
# or
node scripts/setup-wizard.js
The interactive wizard will:
.env file with placeholdersm365-unified-skill (or your choice)m365-unified-secret| Feature | Permissions |
|---|---|
| Email (read) | Mail.Read |
| Email (send) | Mail.Send |
| Email (full) | Mail.ReadWrite |
| SharePoint | Sites.ReadWrite.All |
| OneDrive | Files.ReadWrite.All |
| Planner | Tasks.ReadWrite, Group.Read.All |
| Webhooks | User.Read (minimum for validation) |
From the app registration Overview page, copy:
M365_CLIENT_IDM365_TENANT_IDCopy the template and fill in your values:
cp config/template.env .env
Edit .env:
# Required - Authentication
M365_TENANT_ID="<your-tenant-id>"
M365_CLIENT_ID="<your-client-id>"
M365_CLIENT_SECRET="<your-client-secret>"
# Optional - Feature Toggles
M365_ENABLE_EMAIL=true
M365_ENABLE_SHAREPOINT=false
M365_ENABLE_ONEDRIVE=false
M365_ENABLE_PLANNER=false
M365_ENABLE_WEBHOOKS=false
# Optional - Module Config
M365_MAILBOX="user@domain.com"
M365_SHARED_MAILBOXES="team1@domain.com,team2@domain.com"
M365_SHAREPOINT_SITE_ID="<tenant>.sharepoint.com,<site-guid>,<web-guid>"
M365_PLANNER_GROUP_ID="<m365-group-id>"
M365_WEBHOOK_URL="https://your-domain.com/webhook/m365"
M365_WEBHOOK_SECRET="<generate-random-secret>"
npm test
# or
node scripts/test-connection.js
import { createM365Client } from './skills/m365-unified/src/index.js';
const m365 = await createM365Client({
tenantId: process.env.M365_TENANT_ID,
clientId: process.env.M365_CLIENT_ID,
clientSecret: process.env.M365_CLIENT_SECRET,
mailbox: process.env.M365_MAILBOX,
sharepointSiteId: process.env.M365_SHAREPOINT_SITE_ID,
plannerGroupId: process.env.M365_PLANNER_GROUP_ID,
enableEmail: true,
enableSharepoint: true,
enablePlanner: true,
enableWebhooks: false,
});
// Send email
await m365.email.send({
to: ['recipient@domain.com'],
subject: 'Hello',
body: '<p>Message</p>',
attachments: [{ name: 'file.pdf', contentBytes: 'base64...' }]
});
// List recent emails
const messages = await m365.email.list({ top: 10, folder: 'inbox' });
// Search emails
const results = await m365.email.search('from:client', { top: 20 });
// Move email to folder
await m365.email.move(messageId, folderId);
// Mark as read
await m365.email.markAsRead(messageId);
// Save attachment to SharePoint
await m365.email.saveAttachmentToSharePoint(
messageId,
attachmentId,
'/Documents/Invoices'
);
// Upload file
const file = await m365.sharepoint.upload('/Documents/file.pdf', content, {
contentType: 'application/pdf',
});
// Download file
const content = await m365.sharepoint.download('/Documents/file.pdf');
// List folder contents
const files = await m365.sharepoint.listFiles('/Documents');
// Delete file
await m365.sharepoint.delete('/Documents/old-file.pdf');
// Upload to OneDrive
const file = await m365.onedrive.upload('/Attachments/invoice.pdf', content);
// Download from OneDrive
const content = await m365.onedrive.download('/Attachments/invoice.pdf');
// List OneDrive root
const files = await m365.onedrive.listFiles();
// List all plans
const plans = await m365.planner.listPlans();
// List tasks in a plan
const tasks = await m365.planner.listTasks(planId);
// Create task
await m365.planner.createTask(planId, 'Task Title', {
priority: 3, // 1=urgent, 3=normal, 5=low
dueDateTime: '2026-04-25T00:00:00Z',
description: 'Task description',
bucketId: 'bucket-id', // optional
});
// Create task from email (automation)
await m365.planner.createTaskFromEmail(messageId, planId, {
bucketId: 'bucket-id', // optional
priority: 3,
});
// Update task
await m365.planner.updateTask(taskId, {
percentComplete: 50,
priority: 1,
});
// Delete task
await m365.planner.deleteTask(taskId);
// Create webhook subscription
const subscription = await m365.webhooks.create({
resource: `users/${mailbox}/messages`,
changeType: 'created',
notificationUrl: 'https://your-domain.com/webhook/m365',
expirationDateTime: '2026-04-22T00:00:00Z',
clientState: 'your-secret-token',
});
// List active subscriptions
const subscriptions = await m365.webhooks.list();
// Renew subscription (before expiration)
await m365.webhooks.renew(subscriptionId, '2026-04-25T00:00:00Z');
// Delete subscription
await m365.webhooks.delete(subscriptionId);
Instead of polling with cron jobs, Microsoft Graph sends HTTP POST requests to your webhook URL when:
created)updated, deleted)# Start local webhook handler
node scripts/webhook-handler.js
# Or use the shell script
./scripts/start-webhook.sh
# New emails in inbox
node scripts/manage-webhooks.js create --resource=mail_inbox --type=created
# All mailbox changes
node scripts/manage-webhooks.js create --resource=mail --type=created,updated,deleted
# SharePoint file changes
node scripts/manage-webhooks.js create --resource=sharepoint --type=created,updated
# Planner task changes
node scripts/manage-webhooks.js create --resource=planner --planId=<plan-id> --type=created,updated
{
"value": [
{
"subscriptionId": "subscription-id-guid",
"clientState": "your-secret-token",
"changeType": "created",
"resource": "users/user@domain.com/messages",
"resourceData": {
"@odata.type": "#microsoft.graph.message",
"id": "message-id"
},
"subscriptionExpirationDateTime": "2026-04-22T13:52:00Z"
}
]
}
When you create a subscription, Graph sends a validation request:
POST /webhook/m365
Content-Type: text/plain
Validation-Token: <random-token>
Your endpoint must:
Validation-Token header200 OK and the token value as plain textSee scripts/webhook-handler.js for a reference implementation.
Webhooks expire after 3 days maximum. Set up auto-renewal:
# Cron job example (runs every 6 hours)
0 */6 * * * cd /path/to/m365-unified && node scripts/auto-renew-webhooks.js
| Variable | Required | Description |
|---|---|---|
M365_TENANT_ID | ✅ | Azure AD tenant ID |
M365_CLIENT_ID | ✅ | App registration client ID |
M365_CLIENT_SECRET | ✅ | App registration client secret |
M365_ENABLE_EMAIL | ❌ | Enable email module (default: false) |
M365_ENABLE_SHAREPOINT | ❌ | Enable SharePoint module (default: false) |
M365_ENABLE_ONEDRIVE | ❌ | Enable OneDrive module (default: false) |
M365_ENABLE_PLANNER | ❌ | Enable Planner module (default: false) |
M365_ENABLE_WEBHOOKS | ❌ | Enable webhook features (default: false) |
M365_MAILBOX | ⚠️ | Primary mailbox (required for email features) |
M365_SHARED_MAILBOXES | ❌ | Comma-separated list of shared mailboxes |
M365_SHAREPOINT_SITE_ID | ⚠️ | SharePoint site ID (required for SharePoint) |
M365_ONEDRIVE_USER | ⚠️ | OneDrive user (default: same as M365_MAILBOX) |
M365_PLANNER_GROUP_ID | ⚠️ | M365 Group ID containing Planner plans |
M365_WEBHOOK_URL | ⚠️ | Public webhook endpoint URL (HTTPS required) |
M365_WEBHOOK_SECRET | ⚠️ | Secret for webhook validation |
M365_WEBHOOK_PORT | ❌ | Local webhook handler port (default: 3000) |
# Use Graph Explorer or run:
curl -H "Authorization: Bearer <token>" \
"https://graph.microsoft.com/v1.0/sites"
Response format:
{
"value": [
{
"id": "tenant.sharepoint.com,site-guid,web-guid",
"displayName": "My Site"
}
]
}
Use the full id value for M365_SHAREPOINT_SITE_ID.
# List groups with Planner plans
node scripts/test-planner.js
Or use Graph Explorer:
GET https://graph.microsoft.com/v1.0/groups?$filter=resourceProvisioningOptions/Any(x:x eq 'Team')
# Test connection & authentication
npm test
# Test individual features
npm run test:email
npm run test:sharepoint
npm run test:onedrive
npm run test:planner
# Manage webhooks
npm run webhooks:create -- --resource=mail --type=created
npm run webhooks:list
npm run webhooks:renew -- --id=<subscription-id>
npm run webhooks:delete -- --id=<subscription-id>
.env - Already in .gitignoreBy default, Mail.ReadWrite grants access to ALL mailboxes in the tenant. To restrict:
# Create security group with specific mailboxes
New-DistributionGroup -Name "M365AppAccess" -Type Security
# Add mailboxes to group
Add-DistributionGroupMember -Identity "M365AppAccess" -Member "user@domain.com"
# Create access policy
New-ApplicationAccessPolicy -AppId "CLIENT-ID" -PolicyScopeGroupId "M365AppAccess" -AccessRight RestrictAccess
| Feature | Minimum Permissions | Recommended |
|---|---|---|
| Email (read) | Mail.Read | Mail.Read |
| Email (send) | Mail.Send | Mail.Send |
| Email (full) | Mail.ReadWrite | Mail.ReadWrite |
| SharePoint | Sites.Read.All | Sites.ReadWrite.All |
| OneDrive | Files.Read.All | Files.ReadWrite.All |
| Planner | Tasks.Read, Group.Read | Tasks.ReadWrite, Group.Read.All |
| Webhooks | User.Read | User.Read |
Causes:
Solutions:
.envCauses:
Solutions:
Causes:
Solutions:
tenant.sharepoint.com,site-guid,web-guid)Common Issues:
Webhook URL not publicly accessible
Validation challenge fails
200 with token as plain textclientState matches your secretSubscription expires quickly
No notifications received
User.Read permission minimumMicrosoft Graph uses throttling. If you hit limits:
HTTP 429 Too Many Requests
Retry-After: <seconds>
Solutions:
Retry-After headerThe authentication module is compatible with existing m365-planner configurations. You can:
Same credentials work for both skills - no need to create new app registrations.
@microsoft/microsoft-graph-client ^3.0.7axios ^1.6.0dotenv ^16.3.1express ^4.18.0 (for webhook handler)MIT License - See LICENSE file for details.
docs/ folder for detailed guidesFAQ-DEPENDENCIES.mdBuilt with ❤️ by the OpenClaw Community