A robust, type-safe .NET 8.0+ client library for the Microsoft Graph Mail API that streamlines email operations with an intuitive, async-first design.
MSGraph.Mail.Client provides a comprehensive abstraction layer over the Microsoft Graph Mail API, eliminating boilerplate code and reducing complexity. Whether you're building enterprise email solutions, automation workflows, or integrations, this library offers production-ready functionality with a focus on reliability and developer experience.
Key Capabilities:
- 📧 Mailbox Operations - Read, search, and manage emails with advanced filtering
- 🔐 Flexible Authentication - Support for both interactive and daemon authentication flows
- 📎 Attachment Handling - Seamless attachment retrieval and sending
- 🔄 Efficient Pagination - Built-in support for large email collections
- ⚡ Fully Asynchronous - Non-blocking I/O with Task-based API
- 🎯 Type-Safe Models - Strongly-typed C# models with IntelliSense support
- 🛡️ Production-Ready - Comprehensive error handling and security best practices
- .NET: 8.0 or higher
- Azure: Active Azure AD/Entra ID tenant
- Permissions: Microsoft Graph API access with mail scopes
Install-Package MSGraph.Mail.Clientdotnet add package MSGraph.Mail.Client
Download directly from NuGet.org
Register your application in Microsoft Entra Admin Center:
- Navigate to App registrations → New registration
- Enter an application name and select the appropriate account type
- Configure the Redirect URI:
- Interactive apps:
http://localhost - Daemon apps: Leave blank
- Interactive apps:
- Copy the Application (client) ID and Directory (tenant) ID
- Enable Allow public client flows under Authentication → Advanced settings
Add the following permissions in API permissions:
openid,profile,offline_access(for authentication)user.read(user profile access)mail.readbasic,mail.read(email retrieval)mail.send(sending emails)
Ideal for desktop applications and user-driven scenarios:
var settings = new Settings { ClientId = "your-client-id", TenantId = "your-tenant-id", GraphUserScopes = new[] { "openid", "profile", "offline_access", "user.read", "mail.read", "mail.send" } }; IAuthenticationProvider authProvider = new AuthenticationInteractiveProvider(settings); IEmailGraphService emailService = new EmailGraphService(authProvider);
Best for background services and scheduled jobs:
var settings = new Settings { ClientId = "your-client-id", TenantId = "your-tenant-id", SecretId = "your-client-secret" // Store securely! }; IAuthenticationProvider authProvider = new AuthenticationClientSecretProvider(settings); IEmailGraphService emailService = new EmailGraphService(authProvider);
// Retrieve unread emails with attachments var emails = await emailService.GetEmailsAsync( top: 20, // Items per page limit: 100, // Total items to return (-1 for all) requestInformation: new EmailRequestParameterInformation { IsRead = false, IncludeAttachments = true, EmailOrderby = new[] { new EmailOrderby { OrderbyField = EmailOrderbyField.receivedDateTime, OrderbyType = EmailOrderbyType.Desc } } } ); foreach (var email in emails) { Console.WriteLine($"From: {email.From}"); Console.WriteLine($"Subject: {email.Subject}"); Console.WriteLine($"Received: {email.ReceivedDateTime}"); }
// Search emails by keyword var results = await emailService.GetEmailsAsync( top: 25, limit: 50, requestInformation: new EmailRequestParameterInformation { Search = "project status", IsRead = false } ); // Custom OData filter var filtered = await emailService.GetEmailsAsync( requestInformation: new EmailRequestParameterInformation { Filter = "from/emailAddress/address eq '[email protected]'" } );
// Retrieve attachments from email var attachments = await emailService.GetEmailAttachments(messageId); foreach (var attachment in attachments) { Console.WriteLine($"Attachment: {attachment.Name}"); Console.WriteLine($"Size: {attachment.Size} bytes"); Console.WriteLine($"Type: {attachment.ContentType}"); // Save to disk File.WriteAllBytes($"downloads/{attachment.Name}", attachment.ContentBytes); }
// Send email with HTML content and attachments var emailMessage = new EmailMessage { ToRecipients = new[] { "[email protected]" }, CcRecipients = new[] { "[email protected]" }, Subject = "Project Update", BodyType = EmailBodyType.Html, BodyContent = @" <h2>Q3 Status Report</h2> <p>Please find the attached project update.</p> <p>Best regards,<br/>Project Team</p> ", HasAttachments = true, FileAttachments = new[] { new EmailFileAttachment { Name = "Q3_Report.pdf", ContentType = "application/pdf", ContentBytes = File.ReadAllBytes("reports/Q3_Report.pdf") } } }; await emailService.SendEmail(emailMessage);
// Process large batches efficiently const int batchSize = 50; int processed = 0; var emails = await emailService.GetEmailsAsync( top: batchSize, limit: -1, // Get all emails requestInformation: new EmailRequestParameterInformation { IsRead = false } ); foreach (var email in emails) { // Process each email await ProcessEmail(email); processed++; if (processed % 100 == 0) Console.WriteLine($"Processed {processed} emails..."); }
// Find and categorize emails by sender var importantDomains = new[] { "@executive.company.com", "@board.company.com" }; var allEmails = await emailService.GetEmailsAsync( top: 50, limit: -1 ); var importantEmails = allEmails .Where(e => importantDomains.Any(d => e.From?.EndsWith(d) == true)) .OrderByDescending(e => e.ReceivedDateTime) .ToList(); Console.WriteLine($"Found {importantEmails.Count} important emails");
// Mark emails as read in batches var unreadEmails = await emailService.GetEmailsAsync( top: 50, limit: 500, requestInformation: new EmailRequestParameterInformation { IsRead = false }, markRead: true // Automatically mark as read ); Console.WriteLine($"Archived {unreadEmails.Count} emails");
🎯 Major Updates:
- Framework: Upgraded to .NET 8.0 LTS (from .NET 6.0)
- Documentation: Complete XML documentation for IntelliSense support
- Security: All dependencies updated to latest secure versions
- Azure.Identity: 1.13.1
- Microsoft.Identity.Client: 4.66.1
- Microsoft.Graph: 5.60.0
✨ Quality Improvements:
- Zero compiler warnings with strict null-safety checks
- Refactored orderby logic for improved maintainability
- Enhanced exception handling with better error propagation
- Comprehensive inline documentation for all public APIs
- README and LICENSE included in NuGet package
🔧 Technical Enhancements:
- Improved null reference handling
- Cleaner code patterns with reduced duplication
- Better validation of input parameters
- Production-ready error scenarios
Represents a complete email message with headers and body content:
public class EmailMessage { public string? Id { get; set; } public string? Subject { get; set; } public string? From { get; set; } public IList<string?>? ToRecipients { get; set; } public IList<string?>? CcRecipients { get; set; } public IList<string?>? BccRecipients { get; set; } public string? BodyContent { get; set; } public EmailBodyType? BodyType { get; set; } // Text or Html public bool? HasAttachments { get; set; } public IList<EmailFileAttachment>? FileAttachments { get; set; } public DateTimeOffset? ReceivedDateTime { get; set; } public DateTimeOffset? CreatedDateTime { get; set; } public DateTimeOffset? LastModifiedDateTime { get; set; } }
Represents a file attachment within an email:
public class EmailFileAttachment { public string? Id { get; set; } public string? Name { get; set; } public string? ContentType { get; set; } // MIME type public int? Size { get; set; } // Bytes public bool? IsInline { get; set; } // Embedded vs attached public byte[]? ContentBytes { get; set; } // Binary content }
Advanced query parameters for email retrieval:
public class EmailRequestParameterInformation { public bool? IsRead { get; set; } // Filter by read status public string? Search { get; set; } // Full-text search public string? Filter { get; set; } // OData filter expression public bool? IncludeAttachments { get; set; } public IList<EmailOrderby>? EmailOrderby { get; set; } }
| Issue | Solution |
|---|---|
| "Invalid client ID" | Verify Application ID in Azure Portal matches your settings |
| "Tenant not found" | Ensure Directory ID is correct (use "consumer" for personal accounts) |
| "Insufficient privileges" | Check API permissions in Azure Portal; ensure all required scopes are granted and admin consent is provided |
| "AADSTS50105: User hasn't consented" | Request admin consent for the application in Azure Portal |
| Issue | Solution |
|---|---|
| No emails returned | Verify authenticated user has mailbox access; check IsRead filter |
| Attachment size too large | Graph API limits attachments to 25MB; consider chunked uploads |
| SendEmail timeout | Large attachments require longer timeout periods; increase timeout value |
| "Mail.send permission missing" | Add mail.send scope to application permissions and request consent |
- Batch sizes: Use
topparameter of 50-100 for optimal performance - Pagination: Use
limit: -1carefully; consider implementing incremental loading - Rate limiting: Graph API enforces rate limits; implement exponential backoff for retries
✅ Do:
- Store credentials securely (use Azure Key Vault for production)
- Implement proper error handling and retry logic
- Use appropriate batch sizes for large operations
- Dispose of
IEmailGraphServiceandIAuthenticationProviderafter use - Cache user profiles to minimize API calls
❌ Don't:
- Hardcode credentials in source code
- Ignore pagination limits on large datasets
- Make synchronous wrappers around async methods
- Share authentication provider instances across threads without synchronization
- Store sensitive data in logs
Contributions are welcome! Whether it's bug reports, feature requests, or code contributions:
- Report Issues: Use GitHub Issues with detailed reproduction steps
- Submit PRs: Follow existing code style and include tests
- Documentation: Help improve examples and guides
- Feedback: Share your use cases and improvement suggestions
See CONTRIBUTING.md for detailed guidelines.
For complete working examples, refer to the EmailDemoApp project.
Licensed under the Apache License 2.0 - see LICENSE file for details.
Permissions: commercial use, modification, distribution, private use Conditions: license and copyright notice Limitations: liability, warranty
- Documentation: Microsoft Graph API Docs
- Mail API Reference: Message Resource
- Authentication: Azure Identity Library
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Made with ❤️ for the .NET community