I recently have been working on a project that required a simple contact us form that would send an email to the client containing some basic information on the submission. The form itself was quite simple (just a dropdown select field and a text field), however we ran into some issues when we discovered that the “old-school” System.Net.Mail SmtpClient functionality would not work with their Azure-based Office 365 systems.
Traditionally, I’ve always used SmtpClient to send emails programmatically. It’s pretty fast to implement, as you typically just need a server address, a username and password, a port value, and of course the message, sender and recipient(s).
However in this case, that wouldn’t work. Since the client is using Office 365 for their email delivery, basic authentication could not be used and as such we had to pivot.
The solution was to use a Graph API-based approach, which I was not at all familiar with. This involved the client setting up a new user in Azure Active Directory as well as an AAD Application to handle the requests. For this part, I did not have much involvement as this was all handled on the client’s end. My understanding though is that a dedicated email account was created,
I did have to make some adjustments to how I implemented the code to send the email.
The actual code itself is not all that more complicated than an SmtpClient setup. You still need to authenticate with the service, and still need to build an email message to send. In order to do so, however, you’ll need to include two NuGet packages, Microsoft.Graph and Azure.Identity; the former managing the graph client and message, and the latter controlling access to the API itself.
After adding those two packages to my solution, I simply constructed a message, created a post request body (containing the message as well as an option to save the sent email to the outbox of the sender) and then simply sent the email.
I did run into an issue where the emails did not seem to be sending. I troubleshooted this with the client, and we came across the following error:
Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app ‘xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx’. This issue turned out to be exactly what the error implied; the secret we were using was the secret’s id and not the value itself.
Once I retrieved the actual secret value (using a connection to the client’s Azure key vault) the emails started sending successfully.
The code for this is below. Note that I saved the credentials and sender/receiver information in my appsettings.json. If you’re working with Sitecore, you could pull the emails from a content item if you so wish. In my case the implementation was quite rudimentary so I didn’t need to.
Note that in the below code, I’m passing through the subject line as well as the message from another method. These are just strings, so pass these through however you’d like!
As always, let me know below if you have any questions!
using Microsoft.Extensions.Configuration;
using System.Net.Mail;
using System.Net;
using System.Threading.Tasks;
using Azure.Identity;
using Microsoft.Graph;
using Microsoft.Graph.Models;
using Microsoft.Graph.Users.Item.SendMail;
using Azure.Security.KeyVault.Secrets;
using System;
using Azure.Core;
using Namespace.Helpers.Fields;
using System.Linq;
namespace Namespace.Services.Integrations
{
public interface IEmailSender
{
void SendEmail(string subject, string HtmlMessage);
}
public class EmailSender : IEmailSender
{
public IConfiguration Configuration { get; }
public EmailSender(IConfiguration configuration)
{
Configuration = configuration;
}
public void SendEmail(string subject, string htmlMessage)
{
// Map access to the email service's client secret in the key vault.
SecretClientOptions secretClientOptions = new SecretClientOptions()
{
Retry =
{
Delay = TimeSpan.FromSeconds(2),
MaxDelay = TimeSpan.FromSeconds(16),
MaxRetries = 5,
Mode = RetryMode.Exponential
}
};
// Get the proper key vault URL (based on environment).
string vaultUrl = Configuration["Integrations:KeyVault:Url"];
// Get the secret name.
string secretName = Configuration["Integrations:KeyVault:SecretName"];
// We're using the ManagedIdentityClientId to authenticate. This differs from environment to environment.
var client = new SecretClient(new Uri(vaultUrl), new DefaultAzureCredential(), secretClientOptions);
KeyVaultSecret clientSecret = client.GetSecret(secretName);
string secretValue = clientSecret.Value;
string? tenantId = Configuration["Integrations:Email:TenantId"];
string? clientId = Configuration["Integrations:Email:ClientId"];
ClientSecretCredential credential = new(tenantId, clientId, secretValue);
GraphServiceClient graphClient = new(credential);
Message message = new Message()
{
Subject = subject,
Body = new ItemBody()
{
ContentType = BodyType.Html,
Content = htmlMessage
}
};
if (!Configuration["Integrations:Email:Recipients"].IsNullOrWhiteSpace())
{
var recipients = Configuration["Integrations:Email:Recipients"].Split(",");
var toRecipients = new System.Collections.Generic.List<Recipient>();
if (recipients.Any())
{
foreach (var recipient in recipients) {
var individualRecipient = new Recipient()
{
EmailAddress = new EmailAddress()
{
Address = recipient
}
};
toRecipients.Add(individualRecipient);
}
}
message.ToRecipients = toRecipients;
}
SendMailPostRequestBody body = new SendMailPostRequestBody()
{
Message = message,
SaveToSentItems = true
};
graphClient.Users[Configuration["Integrations:Email:Sender"]].SendMail.PostAsync(body);
}
}
}

Leave a comment