There are no examples anywhere on how to submit an http mail attachment to the Graph API. After 102 unsuccessful permutations, I am asking here. I have come to the conclusion that Graph has a bug in not allowing attachments - or is something known to only those who know a secret handshake.
The context is a standalone program using the ConfidentialClientApplicationBuilder (e.g. https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-daemon-dotnet-acquire-token). Emails are submitting using an httpClient with URL = $"https://graph.microsoft.com/v1.0/users/{userId}/sendMail" and not "$"https://graph.microsoft.com/v1.0/me/sendMail"
I can submit emails just fine. Just not attachments.
Here are 3 examples that do not work and one that did work using Powershell (but not c#, alas).
- Fails on .SendAsync {
{
error": {
"code": "RequestBodyRead",
"message": "The property 'AdditionalData' does not exist on type 'microsoft.graph.attachment'. Make sure to only use property names that are defined by the type or mark the type as open type."
}}
/*
* THIS WORKS WITHOUT ATTACHMENTS. FAILS WITH ATTACHMENTS
*
* "The property 'AdditionalData' does not exist on type 'microsoft.graph.attachment'.
* Make sure to only use property names that are defined by the type or mark the type as open type."
*/
var thisWorksWithoutAttachments = new
{
message = new
{
subject = "Test Email",
body = new
{
contentType = "Text",
content = "Hello, this is a test email!"
},
toRecipients = new[]
{
new
{
emailAddress = new
{
address = "******@outlook.com"
}
}
},
attachments = new List<Microsoft.Graph.Models.Attachment>() // <== note Attachment but with an instance of FileAttachment per https://stackoverflow.com/questions/76152485/how-to-add-attachment-to-microsoft-graph-based-email
{
new Microsoft.Graph.Models.FileAttachment
{
Name = "test.txt",
ContentType = "text/plain",
ContentBytes = Encoding.UTF8.GetBytes("Hello, this is a test attachment!"),
OdataType = "#microsoft.graph.fileAttachment",
IsInline = false,
Size = 1024,
}
}
},
saveToSentItems = "true"
};
var requestContent = new StringContent(JsonSerializer.Serialize(thisWorksWithoutAttachments));
var userId = "******@yyy.onmicrosoft.com";
requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var request = new HttpRequestMessage(HttpMethod.Post, $"https://graph.microsoft.com/v1.0/users/{userId}/sendMail")
{ Content = requestContent };
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
var response = await httpClient.SendAsync(request);
- Fails similarly on .SendAsync. It's the same as above but the email is constructed using objects
{
"error": {
"code": "RequestBodyRead",
"message": "The property 'AdditionalData' does not exist on type 'microsoft.graph.attachment'. Make sure to only use property names that are defined by the type or mark the type as open type."
}}
/*
* FAILS WITH ATTACHMENTS
*
* "The property 'ContentBytes' does not exist on type 'microsoft.graph.attachment'.
* Make sure to only use property names that are defined by the type or mark the type as open type."
*/
var thisFailsMessage = new EmailMessage
{
Message = new Message
{
Subject = Random.Shared.Next() + " Text",
Body = new ItemBody
{
ContentType = BodyType.Text.ToString(),
Content = Random.Shared.Next() + " Hello, this is a test email! " + Random.Shared.Next()
},
ToRecipients =
[
new Recipient
{
EmailAddress = new EmailAddress
{
Address = "******@outlook.com"
}
}
],
Attachments =
[
new FileAttachment
{
Name = "test.txt",
ContentType = "text/plain",
OdataType = "#microsoft.graph.fileAttachment", // <== this not needed since it is set in the FileAttachment
AdditionalData = new Dictionary<string, object>()
{
{ "contentId", "1234" },
{ "lastModifiedDateTime", DateTimeOffset.UtcNow },
{ "isInline", false },
{ "size", 1024 },
{ "contentBytes", Encoding.UTF8.GetBytes("Hello, this is a test attachment!") }
}
}
]
},
SaveToSentItems = true
};
var requestContent = new StringContent(JsonSerializer.Serialize(thisWorksWithoutAttachments));
var userId = "******@yyy.onmicrosoft.com";
requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var request = new HttpRequestMessage(HttpMethod.Post, $"https://graph.microsoft.com/v1.0/users/{userId}/sendMail")
{ Content = requestContent };
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
var response = await httpClient.SendAsync(request);
- Uses MultipartFormDataContent but Fails on .PostAsync {
{
"error": {
"code": "RequestBodyRead",
"message": "A supported MIME type could not be found that matches the content type of the response. None of the supported type(s) 'Microsoft.OData.ODataMediaType, Microsoft.OData.ODataMediaType, Microsoft.OData.ODataMediaType, Microsoft.OData.ODataMediaTyp...' matches the content type 'multipart/form-data; boundary=\"Upload----02/15/2025 12:16:12\"'."
}}
static async Task SendMostafaAsync(HttpClient httpClient, string bearerToken)
{
var userId = "******@yyy.onmicrosoft.com";
var emailEndpoint = $"https://graph.microsoft.com/v1.0/users/{userId}/sendMail";
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("multipart/mixed"));
var filePath = "C:\\Users\\xxx\\source\\repos\\ms-identity-docs-code-dotnet\\console-daemon\\MostafaCode.png";
using var multipartFormContent = new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture));
// Load the file and set the file's Content-Type header
var fileStreamContent = new StreamContent(File.OpenRead(filePath));
fileStreamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); // also tried 'image/png'
// Add the file to the form content
multipartFormContent.Add(fileStreamContent, "file", Path.GetFileName(filePath));
// Add other form data if needed
multipartFormContent.Add(new StringContent("******@outlook.com"), "to");
multipartFormContent.Add(new StringContent("Subject of the email"), "subject");
multipartFormContent.Add(new StringContent("Body of the email"), "body");
// Create the HttpRequestMessage
var request = new HttpRequestMessage(HttpMethod.Post, emailEndpoint)
{
Content = multipartFormContent
};
// Send the request
// var response = await httpClient.SendAsync(request);
// Send the request
var response = await httpClient.PostAsync(emailEndpoint, multipartFormContent);
}
But THIS Powershell script DOES work. Why not the C#?
#Get File Name and Base64 string
$Attachment="C:\Users\\xxx\source\repos\ms-identity-docs-code-dotnet\console-daemon\MostafaCode.png"
$FileName=(Get-Item -Path $Attachment).name
$base64string = [Convert]::ToBase64String([IO.File]::ReadAllBytes($Attachment))
#Connect to GRAPH API $tokenBody = @{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
Client_Id = $clientId
Client_Secret = $clientSecret
}
$tokenResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" -Method POST -Body $tokenBody
$headers = @{
"Authorization" = "Bearer $($tokenResponse.access_token)"
"Content-type" = "application/json"
}
#Send Mail
$URLsend = "https://graph.microsoft.com/v1.0/users/$MailSender/sendMail"
$Body = @" {
"message": {
"subject": "Subject",
"body": {
"contentType": "HTML",
"content": "Body"
},
"toRecipients": [
{
"emailAddress": {
"address": "$Recipient"
}
}
]
,"attachments": [
{
"@odata.type": "#microsoft.graph.fileAttachment",
"name": "$FileName",
"contentType": "text/plain",
"contentBytes": "$base64string"
}
]
},
"saveToSentItems": "false"
}
"@
Invoke-RestMethod -Method POST -Uri $URLsend -Headers $headers -Body $BodyJsonsend