Skip to content

Commit a31707a

Browse files
🔀 ✨ feat: Use draft/upload for attachments, revert to original API when no attachments (#133)
BREAKING: Needs **extra Graph API permission** `Mail.ReadWrite` if adding attachments to file. REMOVE: The `SaveSentItems` option has been removed. Include both original and new way of sending mails. New test console app. Push prerelease to Github packages. Add reproducible build.
1 parent f9b0331 commit a31707a

File tree

15 files changed

+2130
-314
lines changed

15 files changed

+2130
-314
lines changed

.github/workflows/ci.yml

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ name: CI
33
on:
44
pull_request:
55
branches: [main]
6-
push:
7-
branches: [main]
86
workflow_dispatch:
97

108
jobs:
@@ -14,32 +12,37 @@ jobs:
1412
env:
1513
DOTNET_NOLOGO: true
1614
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
15+
MINVERBUILDMETADATA: build.$GITHUB_RUN_ATTEMPT
1716

1817
steps:
19-
- name: Checkout
18+
- name: Checkout
2019
uses: actions/checkout@v2
2120
with:
2221
fetch-depth: 0
2322

24-
- name: .NET SDK
23+
- name: 👷 .NET SDK
2524
uses: actions/setup-dotnet@v1
2625
with:
2726
dotnet-version: "6.0"
2827

29-
- name: Install dependencies
28+
- name: Install dependencies
3029
run: dotnet restore
3130

32-
- name: Build
33-
run: dotnet build --configuration Release --no-restore
31+
- name: 🔨 Build
32+
run: dotnet build --configuration Release --no-restore /p:MinVerBuildMetadata=${{ github.run_number }}
3433

3534
# # - name: Test
3635
# # run: dotnet test --no-restore --verbosity normal
3736

38-
- name: Pack
37+
- name: 📦 Pack
3938
run: dotnet pack ./src/FluentEmail.Graph/FluentEmail.Graph.csproj -c Release -o ./artifacts --no-build
4039

41-
- name: Artifacts
42-
uses: actions/upload-artifact@v2
43-
with:
44-
name: artifacts
45-
path: artifacts/**/*
40+
# - name: Artifacts
41+
# uses: actions/upload-artifact@v2
42+
# with:
43+
# name: artifacts
44+
# path: artifacts/**/*
45+
46+
- name: 🚀 Publish to GitHub packages
47+
run: dotnet nuget push "./artifacts/*.nupkg" --source "https://nuget.pkg.github.com/ESC-BV/index.json" --api-key "${{secrets.GITHUB_TOKEN}}"
48+

FluentEmail.Graph.sln

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentEmail.Graph.Tests", "
1313
EndProject
1414
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E37EFC29-B838-4EC7-8DDD-48303FE8FD52}"
1515
ProjectSection(SolutionItems) = preProject
16-
GitVersion.yml = GitVersion.yml
1716
README.md = README.md
1817
EndProjectSection
1918
EndProject
2019
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{A4341B5B-E943-490B-94AE-D4637A7AF37C}"
2120
EndProject
22-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SendMailSample", "sample\SendMailSample\SendMailSample.csproj", "{5C9C773B-43AF-475C-A56A-96CDA259D4D8}"
21+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SendMailTestApp", "sample\SendMailTestApp\SendMailTestApp.csproj", "{24093217-E352-4B06-9138-C0641657F49D}"
2322
EndProject
2423
Global
2524
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -55,26 +54,26 @@ Global
5554
{B01998E7-3FE2-4A66-968A-0B7725309129}.Release|x64.Build.0 = Release|Any CPU
5655
{B01998E7-3FE2-4A66-968A-0B7725309129}.Release|x86.ActiveCfg = Release|Any CPU
5756
{B01998E7-3FE2-4A66-968A-0B7725309129}.Release|x86.Build.0 = Release|Any CPU
58-
{5C9C773B-43AF-475C-A56A-96CDA259D4D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
59-
{5C9C773B-43AF-475C-A56A-96CDA259D4D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
60-
{5C9C773B-43AF-475C-A56A-96CDA259D4D8}.Debug|x64.ActiveCfg = Debug|Any CPU
61-
{5C9C773B-43AF-475C-A56A-96CDA259D4D8}.Debug|x64.Build.0 = Debug|Any CPU
62-
{5C9C773B-43AF-475C-A56A-96CDA259D4D8}.Debug|x86.ActiveCfg = Debug|Any CPU
63-
{5C9C773B-43AF-475C-A56A-96CDA259D4D8}.Debug|x86.Build.0 = Debug|Any CPU
64-
{5C9C773B-43AF-475C-A56A-96CDA259D4D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
65-
{5C9C773B-43AF-475C-A56A-96CDA259D4D8}.Release|Any CPU.Build.0 = Release|Any CPU
66-
{5C9C773B-43AF-475C-A56A-96CDA259D4D8}.Release|x64.ActiveCfg = Release|Any CPU
67-
{5C9C773B-43AF-475C-A56A-96CDA259D4D8}.Release|x64.Build.0 = Release|Any CPU
68-
{5C9C773B-43AF-475C-A56A-96CDA259D4D8}.Release|x86.ActiveCfg = Release|Any CPU
69-
{5C9C773B-43AF-475C-A56A-96CDA259D4D8}.Release|x86.Build.0 = Release|Any CPU
57+
{24093217-E352-4B06-9138-C0641657F49D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
58+
{24093217-E352-4B06-9138-C0641657F49D}.Debug|Any CPU.Build.0 = Debug|Any CPU
59+
{24093217-E352-4B06-9138-C0641657F49D}.Debug|x64.ActiveCfg = Debug|Any CPU
60+
{24093217-E352-4B06-9138-C0641657F49D}.Debug|x64.Build.0 = Debug|Any CPU
61+
{24093217-E352-4B06-9138-C0641657F49D}.Debug|x86.ActiveCfg = Debug|Any CPU
62+
{24093217-E352-4B06-9138-C0641657F49D}.Debug|x86.Build.0 = Debug|Any CPU
63+
{24093217-E352-4B06-9138-C0641657F49D}.Release|Any CPU.ActiveCfg = Release|Any CPU
64+
{24093217-E352-4B06-9138-C0641657F49D}.Release|Any CPU.Build.0 = Release|Any CPU
65+
{24093217-E352-4B06-9138-C0641657F49D}.Release|x64.ActiveCfg = Release|Any CPU
66+
{24093217-E352-4B06-9138-C0641657F49D}.Release|x64.Build.0 = Release|Any CPU
67+
{24093217-E352-4B06-9138-C0641657F49D}.Release|x86.ActiveCfg = Release|Any CPU
68+
{24093217-E352-4B06-9138-C0641657F49D}.Release|x86.Build.0 = Release|Any CPU
7069
EndGlobalSection
7170
GlobalSection(SolutionProperties) = preSolution
7271
HideSolutionNode = FALSE
7372
EndGlobalSection
7473
GlobalSection(NestedProjects) = preSolution
7574
{110F6221-F88C-44D8-B9E3-B3D1B9691DFD} = {D1C8192B-F35B-4433-9E5F-05F9E92DA58F}
7675
{B01998E7-3FE2-4A66-968A-0B7725309129} = {779D4366-494E-4582-ACDA-38D55AEC2EE5}
77-
{5C9C773B-43AF-475C-A56A-96CDA259D4D8} = {A4341B5B-E943-490B-94AE-D4637A7AF37C}
76+
{24093217-E352-4B06-9138-C0641657F49D} = {A4341B5B-E943-490B-94AE-D4637A7AF37C}
7877
EndGlobalSection
7978
GlobalSection(ExtensibilityGlobals) = postSolution
8079
SolutionGuid = {F11BF11A-54C0-49CA-B567-850A4C6AC40A}

README.md

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
1-
# Fork of NatchEurope/FluentEmail.Graph
2-
3-
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.
4-
5-
[Microsoft Docs on using the Graph API to send large attachments](https://docs.microsoft.com/en-us/graph/outlook-large-attachments?tabs=csharp)
6-
7-
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).
8-
91
# FluentEmail.Graph
102

11-
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).
3+
Sender for [FluentEmail](https://github.com/lukencode/FluentEmail) that
4+
uses [Microsoft Graph API](https://docs.microsoft.com/en-us/graph/api/resources/mail-api-overview?view=graph-rest-1.0).
125

136
![Nuget](https://img.shields.io/nuget/v/FluentEmail.Graph)
147

@@ -34,19 +27,57 @@ Example config in `appsettings.json`
3427
```json
3528
{
3629
"GraphSenderOptions": {
37-
"AppId": "your app id",
3830
"TenantId": "your tenant id",
39-
"Secret": "your secret here",
31+
"ClientId": "your app id",
32+
"Secret": "your secret here"
4033
}
4134
}
4235
```
4336

37+
## v2
38+
39+
The original version only used
40+
the [user: sendMail](https://docs.microsoft.com/en-us/graph/api/user-sendmail?view=graph-rest-1.0&tabs=http) Graph API
41+
endpoint. This could not handle attachments over 3MB.
42+
43+
Starting with v2, if you have included any attachments, the implementation will switch to the following:
44+
45+
- A [draft message](https://docs.microsoft.com/en-us/graph/api/user-post-messages?view=graph-rest-1.0&tabs=http) is
46+
created
47+
- Attachments are added
48+
using [attachment: createUploadSession](https://docs.microsoft.com/en-us/graph/api/attachment-createuploadsession?view=graph-rest-1.0&tabs=http)
49+
- The mail is sent
50+
using [message: send](https://docs.microsoft.com/en-us/graph/api/message-send?view=graph-rest-1.0&tabs=http).
51+
52+
⚠️BREAKING: The `Mail.ReadWrite` permission is required when adding attachments.
53+
54+
[Microsoft Docs on using the Graph API to send large attachments](https://docs.microsoft.com/en-us/graph/outlook-large-attachments?tabs=csharp)
55+
56+
⚠️REMOVED: Unfortunately, the Microsoft Graph API `Send` method does not have a `SaveSentItems` argument like
57+
the `SendMail` method
58+
that was previously used. The `SaveSentItems` option has been removed and there is no way to disable this anymore. (
59+
See [Link 1](https://docs.microsoft.com/en-us/answers/questions/337574/graph-sdk-i-want-to-send-the-saved-draft-mail-but.html)
60+
, [Link 2](https://docs.microsoft.com/en-us/graph/api/message-send?view=graph-rest-1.0&tabs=http),
61+
and [Link 3](https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/743)).
62+
63+
Uploading attachments to a draft message was contributed by [@huntmj01](https://github.com/huntmj01).
64+
65+
## Graph API Permissions
66+
67+
The `Mail.Send` permission must be granted.
68+
69+
Adding attachments? Then the `Mail.ReadWrite` permissions is also required.
70+
4471
## Release
4572

4673
Create new release with creation of new tag on main branch.
4774

48-
Start [publish](https://github.com/ESC-BV/FluentEmail.Graph/actions/workflows/publish.yml) manually, for the new tag. This will push the package to github and nuget.org
75+
Start [publish](https://github.com/ESC-BV/FluentEmail.Graph/actions/workflows/publish.yml) manually, for the new tag.
76+
This will push the package to github and nuget.org
4977

5078
## Origin
5179

52-
Code originally written by [Matt Goldman](https://github.com/matt-goldman) and [merged](https://github.com/lukencode/FluentEmail/pull/218) into FluentEmail repo. But it was not published to NuGet until January 2021. Because we needed this implementation we created a separate repo, modified the code a bit and published it to NuGet.
80+
Code originally written by [Matt Goldman](https://github.com/matt-goldman)
81+
and [merged](https://github.com/lukencode/FluentEmail/pull/218) into FluentEmail repo. But it was not published to NuGet
82+
until January 2021. Because we needed this implementation we created a separate repo, modified the code a bit and
83+
published it to NuGet.

sample/SendMailSample/Program.cs

Lines changed: 0 additions & 63 deletions
This file was deleted.

sample/SendMailSample/SendMailSample.csproj

Lines changed: 0 additions & 25 deletions
This file was deleted.

sample/SendMailSample/appsettings.json

Lines changed: 0 additions & 8 deletions
This file was deleted.

sample/SendMailTestApp/Program.cs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// See https://aka.ms/new-console-template for more information
2+
3+
using FluentEmail.Core;
4+
using FluentEmail.Core.Interfaces;
5+
using FluentEmail.Graph;
6+
using Microsoft.Extensions.Configuration;
7+
using Microsoft.Extensions.DependencyInjection;
8+
9+
namespace SendMailTestApp;
10+
11+
using System.Reflection;
12+
13+
class Program
14+
{
15+
static async Task Main(string[] args)
16+
{
17+
var configurationRoot = GetConfigurationRoot(args);
18+
var graphSenderOptions = configurationRoot.GetRequiredSection("GraphSenderOptions")
19+
.Get<GraphSenderOptions>();
20+
var fromAddress = configurationRoot.GetValue<string>("MailOptions:FromAddress");
21+
22+
var services = new ServiceCollection();
23+
services.AddScoped<IFluentEmailFactory, FluentEmailFactory>();
24+
services.AddFluentEmail(fromAddress)
25+
.AddGraphSender(graphSenderOptions);
26+
var serviceProvider = services.BuildServiceProvider();
27+
28+
// get mail service and out Graph sender
29+
var emailFactory = serviceProvider.GetRequiredService<IFluentEmailFactory>();
30+
var sender = serviceProvider.GetRequiredService<ISender>();
31+
if (sender is not GraphSender)
32+
{
33+
Console.WriteLine("GraphSender not resolved.");
34+
35+
return;
36+
}
37+
38+
Console.WriteLine("Enter destination e-mail address to send test mail to:");
39+
var destinationAddress = Console.ReadLine();
40+
41+
Console.WriteLine("Add attachments? Type 'Y' for yes.");
42+
var addAttachment = (Console.ReadLine() ?? string.Empty).Equals("Y", StringComparison.OrdinalIgnoreCase);
43+
44+
var email = emailFactory.Create();
45+
email.SetFrom(fromAddress)
46+
.To(destinationAddress)
47+
.Subject("Test Email Graph API")
48+
.Body("This is the <b>body</b> of the mail.");
49+
email.Data.IsHtml = true;
50+
51+
if (addAttachment)
52+
{
53+
Console.WriteLine("Adding attachments - this will use another Graph API endpoint that needs the Mail.ReadWrite permission.");
54+
email.AttachFromFilename("TestAttachmentSmall.txt");
55+
email.AttachFromFilename("TestAttachmentLarge.txt");
56+
}
57+
else
58+
{
59+
Console.WriteLine("Not adding attachment - this will use default Graph API endpoint that needs the Mail.Send permission.");
60+
}
61+
62+
var response = await sender.SendAsync(email);
63+
if (response.Successful)
64+
{
65+
Console.WriteLine("Mail sent.");
66+
}
67+
else
68+
{
69+
Console.WriteLine("Mail was NOT sent.");
70+
foreach (var errorMessage in response.ErrorMessages)
71+
{
72+
Console.WriteLine("ERROR");
73+
Console.WriteLine(errorMessage);
74+
}
75+
}
76+
}
77+
78+
private static IConfigurationRoot GetConfigurationRoot(string[] args)
79+
{
80+
var config = new ConfigurationBuilder();
81+
config.AddJsonFile("appsettings.json", optional: true);
82+
83+
var appAssembly = Assembly.Load(new AssemblyName("SendMailTestApp"));
84+
config.AddUserSecrets(appAssembly, optional: false);
85+
86+
config.AddEnvironmentVariables();
87+
88+
if (args is { Length: > 0 })
89+
{
90+
config.AddCommandLine(args);
91+
}
92+
93+
var configurationRoot = config.Build();
94+
95+
return configurationRoot;
96+
}
97+
}

0 commit comments

Comments
 (0)