Building a simple contact form

/ 24 Apr, 2023

In this post, we will build a contact form with Azure Functions and Azure Communication Services.

Introduction

Having a functional contact form on your website is essential. A contact form is a simple yet effective way to receive inquiries and feedback from your visitors, customers, or clients. In this blog post, I will share my experience in building a contact form using Azure Functions and Azure Communication Service’s Email Service.

Why Azure Functions?

Azure Functions is a serverless computing service offered by Microsoft Azure. It allows developers to build, run, and scale applications without having to manage infrastructure. Azure Functions support multiple programming languages, including C#, Java, JavaScript, Python, and PowerShell.

In this case my website is running on a static web app, so I need to use a serverless solution to build the contact form. Azure Functions is a great option because it allows me to build a contact form without having to manage infrastructure or worry about the complexities of communication protocols.

What is Azure Communication Service?

Azure Communication Service is a communication platform offered by Microsoft Azure. It provides developers with the tools and services to integrate real-time communication features such as voice, video, chat, and SMS messaging into their applications. Azure Communication Service also offers an Email Service that allows developers to send and receive emails programmatically.

Building the Contact Form using Azure Functions and Azure Communication Service

To build a contact form using Azure Functions and Azure Communication Service’s Email Service, I followed these steps:

1. Create the Azure resources

The first step was to create the Azure resources. These are the resouces that I used in this sample application:

2. Configure the Azure Communication Services – Email Service

The next step was to configure the Azure Communication Services – Email Service. This includes setting up an email address to send emails. When you create this service, you can choose if you want to use an Azure subdomain or if you want to set up your own domain.

In this case, I used the email address provisioned by an Azure subdomain.

The email address was created and configured automatically:

3. Develop the Azure Function

The first step was to create an Azure Function. You can choose from various templates based on the programming language of your choice. In this case, I used the VS Code to create the C# Azure Function with a HTTP trigger. The Function sends two emails, one to notify the website owner and another one to notify the user who sends the message through the website to let me know that their message was sent.

To call the Azure Function, it is required to send three parameters: nameemail, and message. Also, the Function is using three environment variables that you need to create in the Azure Function configuration:

  • myEmailAddress: to notify the website owner that a new message has been received.
  • senderEmailAddress: the email address that was provisioned in the Azure Communication Service configuration.
  • AzureCommunicationServicesConnectionString: the connection string that was provisioned in the Azure Communication Service configuration.

The EmailClient class is used to send emails using the Azure Communication Service Email Service and it is initialized with the connection string.

The following code snippet shows the HTTP trigger function:

Azure Function HTTP Trigger – SendEmails.cs
        [FunctionName("SendEmails")]
        public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, ILogger log)
        {
            log.LogInformation("SendEmails Function Triggered.");
            string name = req.Query["name"];
            string email = req.Query["email"];
            string message = req.Query["message"];
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;
            email = email ?? data?.email;
            message = message ?? data?.message;
            var myEmailAddress = Environment.GetEnvironmentVariable("myEmailAddress");
            var senderEmailAddress = Environment.GetEnvironmentVariable("senderEmailAddress");            
            var emailClient = new EmailClient(Environment.GetEnvironmentVariable("AzureCommunicationServicesConnectionString"));
            try
            {
                //Email to notify myself
                var selfEmailSendOperation = await emailClient.SendAsync(
                    wait: WaitUntil.Completed,
                    senderAddress: senderEmailAddress,
                    recipientAddress: myEmailAddress,
                    subject: $"New message in the website from {name} ({email})",
                    htmlContent: "<html><body>" + name + " with email address " + email + " sent the following message: <br />" + message + "</body></html>");
                log.LogInformation($"Email sent with message ID: {selfEmailSendOperation.Id} and status: {selfEmailSendOperation.Value.Status}");
                //Email to notify the contact
                var contactEmailSendOperation = await emailClient.SendAsync(
                    wait: WaitUntil.Completed,
                    senderAddress: senderEmailAddress,
                    recipientAddress: email,
                    subject: $"Email sent. Thank you for reaching out.",
                    htmlContent: "Hello " + name + " thank you for your message. Will try to get back you as soon as possible.");
                log.LogInformation($"Email sent with message ID: {contactEmailSendOperation.Id} and status: {contactEmailSendOperation.Value.Status}");
                return new OkObjectResult($"Emails sent.");
            }
            catch (RequestFailedException ex)
            {
                log.LogError($"Sending email operation failed with error code: {ex.ErrorCode}, message: {ex.Message}");
                return new ConflictObjectResult("Error sending email");
            }
        }

4. Develop the front end

In my case I created a simple HTML file and added the necessary form fields for the contact form, name, email, and message.

    <div id="loader"></div>
    <form id="emailForm" onsubmit="sendEmail();return false;">
        <br />
        <label>Name:</label>
        <input type="text" id="name" placeholder="name" required" />
        <br />
        <label>Email:</label>
        <input type="email" id="email" placeholder="email" required" />
        <br />
        <label>Message:</label>
        <textarea placeholder="message" id="message" required"></textarea>
        <br />
        <button type="submit">Submit</button>
    </form>

Here is the JavaScript code that I used to process the call to Azure Function:

        async function sendEmail() {
            const data = {
                name: document.getElementById("name").value,
                email: document.getElementById("email").value,
                message: document.getElementById("message").value
            };
            try {
                showLoader();
                const response = await fetch("https://contactformwithazure.azurewebsites.net/api/SendEmails", {
                    method: "POST",
                    body: JSON.stringify(data),
                    headers: { "Content-Type": "application/json" },
                });
                if (!response.ok) {
                    throw new Error("Network response was not ok");
                }
                setTimeout(location.reload(), 5000);
            } catch (error) {
                console.error("There was an error submitting the form", error);
            }
        }
        function showLoader() {
            document.getElementById("loader").style.display = "block";
            document.getElementById("emailForm").style.display = "none";
        }

5. GitHub Actions to build and deploy the solution

I created a GitHub Action to build and deploy the Azure Function and the website. The following code snippet shows the GitHub Action:

name: Build and Deploy website with Azure Function
on:
  push:
    branches:
      - main
env:
  AZURE_FUNCTIONAPP_PACKAGE_PATH: 'api'
  DOTNET_VERSION: '6.0.x'
jobs:
  deploy-website:
    runs-on: ubuntu-latest
    steps:
      - name: Check Out Repo
        uses: actions/checkout@v2
      - name: Deploy to Azure Static Web Apps
        uses: Azure/static-web-apps-deploy@v1
        with:
          azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
          repo_token: ${{ secrets.GITHUB_TOKEN }}
          action: 'upload'
          app_location: ''
          api_location: '${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}'
          output_location: ''
  build-and-deploy-azure-function:
    runs-on: ubuntu-latest
    steps:
      - name: Check Out Repo
        uses: actions/checkout@v2
      - name: Setup DotNet
        uses: actions/setup-dotnet@v1
        with:
          dotnet-version: ${{ env.DOTNET_VERSION }}
      - name: Build Azure Function
        shell: bash
        run: |
          pushd './${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}'
          dotnet build --configuration Release --output ./output
          popd
      - name: Deploy Azure Function
        uses: Azure/functions-action@v1
        with:
          app-name: 'ContactFormWithAzure'
          slot-name: 'Production'
          package: '${{ env.AZURE_FUNCTIONAPP_PACKAGE_PATH }}/output'
          publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE }}

The GitHub Action is using two secrets that you need to configure:

  • AZUREAPPSERVICE_PUBLISHPROFILE: the publish profile of the Azure Function.
  • AZURE_STATIC_WEB_APPS_API_TOKEN: the API token of the Azure Static Web App.

Testing the solution

I tested the solution by sending a message from the contact form. The following image shows the email that was sent to the website owner and the email that was sent to the user who posted the message.

You can try the solution here.

And check out my production contact form here.