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
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.
No job functions found after Azure functions upgrade
No job functions found. Try making your job classes and methods public. If you're using binding extensions make sure you've called the registration method for t
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