From cce96381f98fe972f1f1ff7674703c77f49c0602 Mon Sep 17 00:00:00 2001 From: huntmj01 Date: Wed, 2 Mar 2022 09:31:35 -0500 Subject: [PATCH] Modifies GraphSender to use an upload session for emails with attachments greater than 3MB --- README.md | 9 +- .../FluentEmailServicesBuilderExtensions.cs | 4 +- src/FluentEmail.Graph/GraphSender.cs | 131 ++++++++++++------ src/FluentEmail.Graph/GraphSenderOptions.cs | 5 - 4 files changed, 100 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index b89c811..130004f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ +# Fork of NatchEurope/FluentEmail.Graph + +Fork of [NatchEurope/FluentEmail.Graph](https://github.com/NatchEurope/FluentEmail.Graph) that modifies the `GraphSender` to use an upload session for sending emails with attachments that are 3MB or larger. I was receiving a Microsoft Graph API error when trying to use FluentEmail.Graph to send emails with attachments over 3MB. + +[Microsoft Docs on using the Graph API to send large attachments](https://docs.microsoft.com/en-us/graph/outlook-large-attachments?tabs=csharp) + +Unfortunately, the Microsoft Graph API `Send` method does not have a `SaveSentItems` argument like the `SendMail` method does, so I had to remove the option to disable saving sent items. See [Link 1](https://docs.microsoft.com/en-us/answers/questions/337574/graph-sdk-i-want-to-send-the-saved-draft-mail-but.html), [Link 2](https://docs.microsoft.com/en-us/graph/api/message-send?view=graph-rest-1.0&tabs=http), and [Link 3](https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/743). + # FluentEmail.Graph Sender for [FluentEmail](https://github.com/lukencode/FluentEmail) that uses [Microsoft Graph API](https://docs.microsoft.com/en-us/graph/api/resources/mail-api-overview?view=graph-rest-1.0). @@ -29,7 +37,6 @@ Example config in `appsettings.json` "AppId": "your app id", "TenantId": "your tenant id", "Secret": "your secret here", - "SaveSentItems": true } } ``` diff --git a/src/FluentEmail.Graph/FluentEmailServicesBuilderExtensions.cs b/src/FluentEmail.Graph/FluentEmailServicesBuilderExtensions.cs index 6b280c0..9b394e6 100644 --- a/src/FluentEmail.Graph/FluentEmailServicesBuilderExtensions.cs +++ b/src/FluentEmail.Graph/FluentEmailServicesBuilderExtensions.cs @@ -21,15 +21,13 @@ public static FluentEmailServicesBuilder AddGraphSender( this FluentEmailServicesBuilder builder, string graphEmailClientId, string graphEmailTenantId, - string graphEmailSecret, - bool saveSentItems = false) + string graphEmailSecret) { var options = new GraphSenderOptions { ClientId = graphEmailClientId, TenantId = graphEmailTenantId, Secret = graphEmailSecret, - SaveSentItems = saveSentItems, }; return builder.AddGraphSender(options); } diff --git a/src/FluentEmail.Graph/GraphSender.cs b/src/FluentEmail.Graph/GraphSender.cs index 2056e66..c0e5849 100644 --- a/src/FluentEmail.Graph/GraphSender.cs +++ b/src/FluentEmail.Graph/GraphSender.cs @@ -19,17 +19,13 @@ /// public class GraphSender : ISender { - private readonly bool saveSent; - + private const int ThreeMb = 3145728; private readonly GraphServiceClient graphClient; public GraphSender(GraphSenderOptions options) { - this.saveSent = options.SaveSentItems ?? true; - - ClientSecretCredential spn = new ClientSecretCredential(options.TenantId, options.ClientId, options.Secret); - - this.graphClient = new (spn); + ClientSecretCredential spn = new(options.TenantId, options.ClientId, options.Secret); + this.graphClient = new(spn); } public SendResponse Send(IFluentEmail email, CancellationToken? token = null) @@ -41,11 +37,9 @@ public async Task SendAsync(IFluentEmail email, CancellationToken? { try { - var message = CreateMessage(email); - await this.graphClient.Users[email.Data.FromAddress.EmailAddress] - .SendMail(message, this.saveSent) - .Request() - .PostAsync(); + var message = await this.CreateMessage(email); + + await this.SendMessage(email, message); return new SendResponse { @@ -61,61 +55,68 @@ await this.graphClient.Users[email.Data.FromAddress.EmailAddress] } } - private static Message CreateMessage(IFluentEmail email) + private async Task CreateMessage(IFluentEmail email) { - var messageBody = new ItemBody + var templateBody = new ItemBody { Content = email.Data.Body, ContentType = email.Data.IsHtml ? BodyType.Html : BodyType.Text, }; - var message = new Message(); - message.Subject = email.Data.Subject; - message.Body = messageBody; - message.From = ConvertToRecipient(email.Data.FromAddress); - message.ReplyTo = CreateRecipientList(email.Data.ReplyToAddresses); - message.ToRecipients = CreateRecipientList(email.Data.ToAddresses); - message.CcRecipients = CreateRecipientList(email.Data.CcAddresses); - message.BccRecipients = CreateRecipientList(email.Data.BccAddresses); + var template = new Message + { + Subject = email.Data.Subject, + Body = templateBody, + From = ConvertToRecipient(email.Data.FromAddress), + ReplyTo = CreateRecipientList(email.Data.ReplyToAddresses), + ToRecipients = CreateRecipientList(email.Data.ToAddresses), + CcRecipients = CreateRecipientList(email.Data.CcAddresses), + BccRecipients = CreateRecipientList(email.Data.BccAddresses), + }; + + Message draft = await this.graphClient + .Users[email.Data.FromAddress.EmailAddress] + .MailFolders + .Drafts + .Messages + .Request() + .AddAsync(template); if (email.Data.Attachments != null && email.Data.Attachments.Count > 0) { - message.Attachments = new MessageAttachmentsCollectionPage(); - - email.Data.Attachments.ForEach( - a => + foreach (var a in email.Data.Attachments) + { + if (a.Data.Length < ThreeMb) { - var attachment = new FileAttachment - { - Name = a.Filename, - ContentType = a.ContentType, - ContentBytes = GetAttachmentBytes(a.Data), - }; - - message.Attachments.Add(attachment); - }); + await this.UploadAttachmentUnder3Mb(email, draft, a); + } + else + { + await this.UploadAttachement3MbOrOver(email, draft, a); + } + } } switch (email.Data.Priority) { case Priority.High: - message.Importance = Importance.High; + draft.Importance = Importance.High; break; case Priority.Normal: - message.Importance = Importance.Normal; + draft.Importance = Importance.Normal; break; case Priority.Low: - message.Importance = Importance.Low; + draft.Importance = Importance.Low; break; default: - message.Importance = Importance.Normal; + draft.Importance = Importance.Normal; break; } - return message; + return draft; } private static IList CreateRecipientList(IEnumerable
addressList) @@ -147,11 +148,61 @@ private static Recipient ConvertToRecipient([NotNull] Address address) }; } + private async Task UploadAttachmentUnder3Mb(IFluentEmail email, Message draft, Core.Models.Attachment a) + { + var attachment = new FileAttachment + { + Name = a.Filename, + ContentType = a.ContentType, + ContentBytes = GetAttachmentBytes(a.Data), + }; + + await this.graphClient + .Users[email.Data.FromAddress.EmailAddress] + .Messages[draft.Id] + .Attachments + .Request() + .AddAsync(attachment); + } + private static byte[] GetAttachmentBytes(Stream stream) { using var m = new MemoryStream(); stream.CopyTo(m); return m.ToArray(); } + + private async Task UploadAttachement3MbOrOver(IFluentEmail email, Message draft, Core.Models.Attachment a) + { + var attachmentItem = new AttachmentItem + { + AttachmentType = AttachmentType.File, + Name = a.Filename, + Size = a.Data.Length, + }; + + var uploadSession = await this.graphClient + .Users[email.Data.FromAddress.EmailAddress] + .Messages[draft.Id] + .Attachments + .CreateUploadSession(attachmentItem) + .Request() + .PostAsync(); + + int maxSliceSize = 320 * 1024; + var fileUploadTask = new LargeFileUploadTask(uploadSession, a.Data, maxSliceSize); + + await fileUploadTask.UploadAsync(); + } + + private async Task SendMessage(IFluentEmail email, Message message) + { + await this.graphClient + .Users[email.Data.FromAddress.EmailAddress] + .Messages[message.Id] + .Send() + .Request() + .PostAsync(); + } } } diff --git a/src/FluentEmail.Graph/GraphSenderOptions.cs b/src/FluentEmail.Graph/GraphSenderOptions.cs index f9dd7a8..c846e72 100644 --- a/src/FluentEmail.Graph/GraphSenderOptions.cs +++ b/src/FluentEmail.Graph/GraphSenderOptions.cs @@ -19,10 +19,5 @@ public class GraphSenderOptions /// Gets or sets the secret string previously shared with AAD at application registration to prove the identity of the application (the client) requesting the tokens. /// public string Secret { get; set; } - - /// - /// Gets or sets a value indicating whether to save the message in Sent Items. Default is true. - /// - public bool? SaveSentItems { get; set; } } }