Creating a Contact Form API with Azure Functions

I’ve been looking into Azure Functions for the last few months, playing around with various experiments when I have had time. For those who haven’t heard of them, Azure Functions are a service that lets you build event based micro services without the need to manage any infrastructure. For someone who runs a static website without a WebApp server or virtual machine, this can add a lot of additional functionality that you would normally need dedicated infrastructure to provide. The other good thing is Azure Functions are priced on execution time and are cheap compared to running a WebApp server.

Contact
Photo by Mathyas Kurmann on Unsplash

For example, having a jQuery contact form rather than publishing an e-mail address would normally require a full web application to submit the form. Instead of converting my site back to a web application and moving away from a CDN delivered site, let’s take a look at building a Contact Form API using Azure Functions.

Getting Started

It’s important to note that Microsoft have some built in tooling in the Azure portal that allows you to create functions directly within the portal without any editor in a variety of languages. Through this article, however, I will be using C# in Visual Studio Community Edition as I am more comfortable with that IDE and it allows me to test and debug locally before deploying the function to Azure.

SendGrid

To follow along with this article you will also need a SendGrid account and an API key generated for your function to use. You can start with Full Access to the SendGrid API’s to start with and restrict it down later as you move into production. It is also beneficial to install the Azure SDK and Azure Storage Emulator so you can test locally before moving onto Azure resources.

Step 1 - Create a Project

Create Project

Firstly, create your Azure Functions project in Visual Studio. Remember Functions should be constructed with a micro-services mind set, so when planning Functions the project you create here should be for a group of functions. In my case I’ve created a functions project to hold all the functions I will need for my website.

Project Details

When you create a new functions project you will also need to select the version you are creating (in this case version 2 using .NET Core) as well as the storage account the function uses and its access rights. At the moment we are selecting the Storage Emulator to test locally, however there is an environment-based variable that holds this setting for when we deploy to Azure. The trigger at this point is the trigger created for a default function and is not important at this stage.

Cleanup

When the project has been created you should have only a few files. If you selected HTTP Trigger, remove the Function1.cs file as it is the default Function and we are going to create one from scratch.

Step 2 - Create the Function

Create Function

I added a new folder to my project called “Contact Form” and added a new Function call ContactPostMessage.cs. All this function will do is accept POST messages from the internet with a specific JSON payload and send them as an e-mail.

Function Settings

This Function should run off a “Http trigger” and have “Function” access rights applied to it. This basically means that anyone who has the Function access key can use HTTP / HTTPS to send data to this API which will convert it to an email. The reason we are using Function as the access rights is we will be putting an API Management front end to wrap any Http trigger Functions together and provide some additional functionality.

Step 3 - Code the function

Firstly, lets create a function.json file to provide the bindings for the input and output of the function. This file must sit in the same directory as your function and is an array of bindings in JSON format. The code for this file looks like this:

{
  "bindings": [
    {
      "authLevel": "function",
      "name": "req",
      "type": "httpTrigger",
      "direction": "in",
      "methods": [
        "post"
      ]
    },
    {
      "name": "$return",
      "type": "http",
      "direction": "out"
    },
    {
      "name": "mail",
      "type": "sendGrid",
      "apiKey": "SENDGRID_API_KEY",
      "to": "CONTACT_TO_ADDRESS",
      "direction": "out"
    }
  ]
}

This file contains one input binding, the httpTrigger type with only post allowed, and two output types, a http return type and SendGrid output type. Now let’s modify the function to accept, check and process a JSON file with the information someone will enter to send us a message. Let’s step through the ContactPostMessage class to show what we are doing with this function:

public static class ContactPostMessage
{               
    [FunctionName("ContactPostMessage")]
    public static async Task<IActionResult> Run( 
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
        [SendGrid(ApiKey = "AzureWebJobsSendGridApiKey")] IAsyncCollector<SendGridMessage> messageCollector,
        ILogger log)
    {

The first few lines define the class and the run function, which is called when the function is triggered. This matches what we have in the function.json file. Firstly we are processing a HttpTrigger and trapping the request in the req variable. We are also injecting a SendGrid Message collector in the variable messageCollector and the logging system into the variable log.

        string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
        string name, email, subject, message = "";
        try
        {
            dynamic data = JsonConvert.DeserializeObject(requestBody);

            name = data?.name;
            email = data?.email;
            subject = data?.subject;
            message = data?.message;
        }
        catch(Exception ex)
        {
            log.LogInformation("Error trying to transform JSON payload - " + ex.ToString());
            return new BadRequestObjectResult("Malformed JSON payload");
        }

In this next segment of code we are reading the body of the request and trying to unpack a JSON object that contains the data of the contact form. We use a stream reader to get the request body, then the JsonConvert class of the Newtonsoft.Json library to unpack the body as a JSON object. We then put the required data into some string variables. We do this in a try-catch method so we can send any errors back if the body of the message doesn’t meet our basic requirements.

        if (Regex.IsMatch(name, @"^[A-Za-z]{3,}$"))
            return new BadRequestObjectResult("Name may only contain alpha-numeric characters");

        if (Regex.IsMatch(subject, @"^[<>%\$!#^&*+=|/`~]$"))
            return new BadRequestObjectResult("Subject may not contain special characters");

        if (Regex.IsMatch(message, @"^[<>%\$!#^&*+=|/`~]$"))
            return new BadRequestObjectResult("Message may not contain special characters");
        try
        {
            var fromAddr = new System.Net.Mail.MailAddress(email);
            if (fromAddr.Address != email)
                return new BadRequestObjectResult("E-Mail address is not valid");
        }
        catch
        {
            return new BadRequestObjectResult("E-Mail address is not valid");
        }

Once we have done the basic checks, we use Regex to do a more complete check of the data elements themselves, making sure we have a valid email address, message, subject and name.

        var mail = new SendGridMessage();
        mail.AddTo(Environment.GetEnvironmentVariable("CONTACT_TO_ADDRESS", EnvironmentVariableTarget.Process));
        mail.SetFrom(email, name);
        mail.SetSubject(subject);
        mail.AddContent("text/html", message);

        await messageCollector.AddAsync(mail);

        return new OkResult();
    }
}

Finally, we compose the JSON payload into a SendGrid message and send it using the injected collector. Once this is all done, deploy the functions project to Azure, I use Azure DevOps to publish connected to my repository. For the code to work you need the following variables added to your published resource (in the configuration section of the platform features area):

  • AzureWebJobsSendGridApiKey - Your SendGrid API Key
  • AzureWebJobsStorage - The Storage account to associate with this function app
  • CONTACT_TO_ADDRESS - The to address to send all generated e-mails to.

Step 4 - Present the function using API Management

You can just publish a function as is, however I put mine behind an API Management gateway so I can bundle any further functions up into a set of products, but it also allows me to specify advanced policies, such as rate limits, to help protect from people spamming the API.

Create from Function

API Management has an option to allow you to link directly with an Azure function and it will set up the correct access mechanisms in the background. Once this is setup, you can publish a number of functions through a single front end.

Summary

So that is the basics of creating an Azure Function. Quite useful for implementing fire and forget workloads. You can find the example code for this function as a GitHub Project here.

Glenn Prince

Glenn Prince

Glenn is Solution Architect from Brisbane Australia. With almost 20 years of misadventures in IT he is still passionate, somewhat opinionated and slightly crazy. He also likes to pretend he can develop software and be creative. All opinions are his own.