Error sending email via SendGrid from Azure Functions
If you’re seeing an error sending email via SendGrid from Azure Functions v4 Isolated model saying “System.Private.CoreLib: Exception while executing function: Functions.SendEmail. Microsoft.Azure.WebJobs.Extensions.SendGrid: A ‘To’ address must be specified for the message.” then you have a JSON serialization problem!
The standard JSON in the newer Azure functions projects is System.Text.Json, which is serialising the returned SendGridMessage incorrectly. So once the SendGridMessage gets to the SendGrid Library, it’s failing as it doesn’t think you’ve provided the correct information.
To remedy this, you need to change the serialization in your Azure Functions app back to NewtonsoftJson.
To resolve this issue you need to override your ConfigureFunctionsWebApplication method on the HostBuilder like below.
var host = new HostBuilder()
.ConfigureFunctionsWebApplication(workerApplication =>
{
workerApplication.Services.Configure<WorkerOptions>(workerOptions =>
{
var settings = NewtonsoftJsonObjectSerializer.CreateJsonSerializerSettings();
settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
settings.NullValueHandling = NullValueHandling.Ignore;
workerOptions.Serializer = new NewtonsoftJsonObjectSerializer(settings);
});
}).ConfigureServices((context, services) =>
{
var connectionString = context.Configuration.GetValue<string>("AzureWebJobsStorage");
services.AddSingleton(new TableServiceClient(connectionString));
})
.ConfigureLogging((hostingContext, logging) =>
{
var raygunKey = hostingContext.Configuration.GetValue<string>("RaygunKey");
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("Worker", LogEventLevel.Warning)
.MinimumLevel.Override("Host", LogEventLevel.Warning)
.MinimumLevel.Override("System", LogEventLevel.Error)
.MinimumLevel.Override("Function", LogEventLevel.Debug)
.MinimumLevel.Override("Azure.Storage", LogEventLevel.Error)
.MinimumLevel.Override("Azure.Core", LogEventLevel.Error)
.MinimumLevel.Override("Azure.Identity", LogEventLevel.Error)
.Enrich.WithProperty("Application", "Demo Functions")
.Enrich.FromLogContext()
.WriteTo.Console(LogEventLevel.Debug)
.WriteTo.File("log.txt", LogEventLevel.Debug, rollingInterval: RollingInterval.Day)
.WriteTo.Raygun(raygunKey, restrictedToMinimumLevel: LogEventLevel.Error)
.CreateLogger();
logging.AddSerilog(Log.Logger, true);
})
.Build();
If you want to make this a little tidier then you can create an extension method for the IFunctionsWorkerApplicationBuilder and move this code into the method like below
public static class WorkerConfigurationExtensions
{
public static IFunctionsWorkerApplicationBuilder UseNewtonsoftJson(this IFunctionsWorkerApplicationBuilder builder)
{
builder.Services.Configure<WorkerOptions>(workerOptions =>
{
var settings = NewtonsoftJsonObjectSerializer.CreateJsonSerializerSettings();
settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
settings.NullValueHandling = NullValueHandling.Ignore;
workerOptions.Serializer = new NewtonsoftJsonObjectSerializer(settings);
});
return builder;
}
}
Once you have this in place, you can change your programs.cs to use that extension method.
var host = new HostBuilder()
.ConfigureFunctionsWebApplication(workerApplication =>
{
workerApplication.UseNewtonsoftJson();
})
.ConfigureServices((context, services) =>
{
var connectionString = context.Configuration.GetValue<string>("AzureWebJobsStorage");
services.AddSingleton(new TableServiceClient(connectionString));
})
.ConfigureLogging((hostingContext, logging) =>
{
var raygunKey = hostingContext.Configuration.GetValue<string>("RaygunKey");
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("Worker", LogEventLevel.Warning)
.MinimumLevel.Override("Host", LogEventLevel.Warning)
.MinimumLevel.Override("System", LogEventLevel.Error)
.MinimumLevel.Override("Function", LogEventLevel.Debug)
.MinimumLevel.Override("Azure.Storage", LogEventLevel.Error)
.MinimumLevel.Override("Azure.Core", LogEventLevel.Error)
.MinimumLevel.Override("Azure.Identity", LogEventLevel.Error)
.Enrich.WithProperty("Application", "Demo Functions")
.Enrich.FromLogContext()
.WriteTo.Console(LogEventLevel.Debug)
.WriteTo.File("log.txt", LogEventLevel.Debug, rollingInterval: RollingInterval.Day)
.WriteTo.Raygun(raygunKey, restrictedToMinimumLevel: LogEventLevel.Error)
.CreateLogger();
logging.AddSerilog(Log.Logger, true);
})
.Build();
host.Run();
Once this is in place, run your code again and you should get emails sent without issue.
Simon Holman
.NET and Azure Developer
I write about .NET, Azure, and cloud development. Follow along for tips, tutorials, and best practices.
Related Posts
Sending email from Azure Functions with SendGrid
Azure Functions is the perfect tool for sending emails from websites and apps. It's lightweight, fast, and scalable. Add SendGrid to it and you have a match
Debug Azure Functions webhook callbacks with Dev Tunnels
Using Dev Tunnels to test your Azure Functions webhook callbacks is a breeze.
Configure Serilog for Logging in Azure Functions
Even though the built-in logging in .NET Core is pretty good these days, Serilog takes it to the next level, making complex logging exceptionally easy.