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.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top