Create an email subscription with Azure Functions - Part 3
Tags: azureapril, azure, webdev, tutorial
Published: 2019-04-12 15:16:35

---
title: Create an email subscription with Azure Functions - Part 3
published: true 
description: How to generate a weekly digest email for a blog using Azure functions, SendGrid and Azure Storage
tags: azureapril, azure, webdev, tutorial
series: Azure April
cover_image: https://thepracticaldev.s3.amazonaws.com/i/pblyvmtj4y8bdqisyzey.jpeg
---

*We've selected our favorite tips and tricks created by [Michael Crump](https://twitter.com/mbcrump) and are delivering fresh technical content on Azure all April! Miss a day (or more)? [Catch up with the series](https://dev.to/t/azureapril).*

**Don't have Azure?** [Grab a free subscription](https://azure.microsoft.com/en-us/free?&WT.mc_id=azureapril_devto-blog-cxa). 

This is the third post in a 4 part series on how to build a serverless email subscription service with [Azure Functions](https://azure.microsoft.com/services/functions/?WT.mc_id=azureapril_devto-blog-cxa).

Full Source Code The source code for the app can be found on [GitHub](https://github.com/mbcrump/EmailSubscription)

[Part 1 - What we're going to build and how to build it](http://www.michaelcrump.net/azure-tips-and-tricks97/)
[Part 2 - Storing Emails using Azure Table Storage](http://www.michaelcrump.net/azure-tips-and-tricks98/)
[Part 3 - Writing the Frontend with HTML5 and jQuery](Part 3 - Writing the Frontend with HTML5 and jQuery)
[Part 4 - Sending Emails with Sendgrid and Azure Functions](http://www.michaelcrump.net/azure-tips-and-tricks100/)

We're trying to build an Email Subscription similar to the following. 

![](https://thepracticaldev.s3.amazonaws.com/i/1i1bl0nqnic47xjf5bxd.png)

## Continuing where we left off

In our last post, we left off by creating an Azure Function that had the ability to accept a POST request and store data using Azure Table Storage for our email address. While this works great in something like Postman, we need to create a way to allow a user to interact with it.

Now is a great time to go ahead and publish our Azure Function. Simply right-click the project name and select Publish, then Publish again as shown below.

![](https://thepracticaldev.s3.amazonaws.com/i/ij2gi983tdkjzyacosry.png)

Once it deploys, if you click on the StoreEmail function, then you'll see a Get Function URL as shown below.

![](https://thepracticaldev.s3.amazonaws.com/i/1bamciuitfu1ec2qmrdt.png)

If you click it, then copy and paste the Function url as you'll use that later.

![](https://thepracticaldev.s3.amazonaws.com/i/3lbp407bqpsjggmv0cph.png)

## Dealing with CORS
While we're in the portal, click on your Azure Function and under Platform Features, you'll see API and then CORS.

Cross-Origin Resource Sharing (CORS) allows JavaScript code running in a browser on an external host to interact with your backend.

Since we'll be moving this to a web host shortly, you'll want to specify your domain name. Here is mine:

## Finally, the frontend.

Create a new .HTML page anywhere and begin by adding the following code:

```
<div class="style-1')">
  <h1>Subscribe to Michael's Weekly Blog Digest</h1>
  <i>I'll send you an email once a week of all my recent blog posts and notable Azure news.</i>
</div>
<form id="target')">
  <ul class="form-style-1')">
    <div id="contactForm')">
       Email: <span class="required')">*</span>
       <li><input type="text" name="fromEmail" /></li>
       <li>
         <br/>
         <input type="submit" value="Send!" /></li>
     </div>
  </ul>
</form>
<div id="contactFormStatus" class="style-1')" />
```

This gives us a form along with an input type for our email address and a submit button. We also have a div tag at the bottom which we'll use to show status of the request.

Now we'll use jQuery and a Ajax call to POST the email address to our Azure Function url that we copied earlier. We will be using a regex that I stole off the web for additional verification they are supplying a valid email address.

```
<script src="https://code.jquery.com/jquery-3.1.1.min.js')"></script>
<script type="text/javascript')">
  var url = "https://functionsendemails.azurewebsites.net/api/StoreEmail?code=hKngrr6sq35GJ8cb6al4K6oP0cRphKNDMXpLrCtoNCJzM0ZDwJNkJQ==";
  $("form").on('submit', function (event) {
    event.preventDefault();
    var data = $(this).serialize();
         $("#target :input").prop("disabled", true);
         var reg = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i
         var address = $('input:text').val();
            if(reg.test(address) == false) {
                // Validation Failed
                $("#contactFormStatus").html("Enter a valid email address").css({ 'color': 'red', 'font-size': '110%' });;
                $("#target :input").prop("disabled", false);
            }
            else {
            $.ajax({
                type: "POST",
                url: url,
                data: data,
                dataType: "text",
                headers: {'Content-Type': 'application/x-www-form-urlencoded'},
                success: function (respData) {
                    $("#contactFormStatus").html(respData).css({ 'color': 'green', 'font-size': '100%' });;
                    $("#target :input").prop("disabled", true);
                },
                error: function (jqXHR) {
                    $("#contactFormStatus").html("An error occurred: " + jqXHR.responseText).css({ 'color': 'red', 'font-size': '110%' });;
                    $("#target :input").prop("disabled", false);
                }
            });
            }
     });
 </script>
```

We'll now put in some CSS to make the site look prettier.

```
<style type="text/css')">
.style-1 {
  margin: 10px auto;
  max-width: 400px;
  padding: 20px 12px 10px 20px;
  font: 13px "Lucida Sans Unicode", "Lucida Grande", sans-serif;
}

.form-style-1 {
  margin: 10px auto;
  max-width: 400px;
  padding: 20px 12px 10px 20px;
  font: 13px "Lucida Sans Unicode", "Lucida Grande", sans-serif;
}

.form-style-1 li {
  padding: 0;
  display: block;
  list-style: none;
  margin: 10px 0 0 0;
}

.form-style-1 label {
  margin: 0 0 3px 0;
  padding: 0px;
  display: block;
  font-weight: bold;
}

.form-style-1 input[type=text],
.form-style-1 input[type=date],
.form-style-1 input[type=datetime],
.form-style-1 input[type=number],
.form-style-1 input[type=search],
.form-style-1 input[type=time],
.form-style-1 input[type=url],
.form-style-1 input[type=email],
textarea,
select {
  box-sizing: border-box;
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  border: 1px solid #BEBEBE;
  padding: 7px;
  margin: 0px;
  -webkit-transition: all 0.30s ease-in-out;
  -moz-transition: all 0.30s ease-in-out;
  -ms-transition: all 0.30s ease-in-out;
  -o-transition: all 0.30s ease-in-out;
  outline: none;
}

.form-style-1 input[type=text]:focus,
.form-style-1 input[type=date]:focus,
.form-style-1 input[type=datetime]:focus,
.form-style-1 input[type=number]:focus,
.form-style-1 input[type=search]:focus,
.form-style-1 input[type=time]:focus,
.form-style-1 input[type=url]:focus,
.form-style-1 input[type=email]:focus,
.form-style-1 textarea:focus,
.form-style-1 select:focus {
  -moz-box-shadow: 0 0 8px #88D5E9;
  -webkit-box-shadow: 0 0 8px #88D5E9;
  box-shadow: 0 0 8px #88D5E9;
  border: 1px solid #88D5E9;
}

.form-style-1 .field-divided {
  width: 49%;
}

.form-style-1 .field-long {
  width: 100%;
}

.form-style-1 .field-select {
  width: 100%;
}

.form-style-1 .field-textarea {
  height: 100px;
}

.form-style-1 input[type=submit],
.form-style-1 input[type=button] {
  background: #4B99AD;
  padding: 8px 15px 8px 15px;
  border: none;
  color: #fff;
}

.form-style-1 input[type=submit]:hover,
.form-style-1 input[type=button]:hover {
  background: #4691A4;
  box-shadow: none;
  -moz-box-shadow: none;
  -webkit-box-shadow: none;
}

.form-style-1 .required {
  color: red;
}
</style>
```

If you try to run the code on your server then you'll see it works as designed. You can also use something like Azure Storage Explorer to see the data in the table.

![](https://thepracticaldev.s3.amazonaws.com/i/vdx2ak2dlqc6i4w1fejc.gif)

That's it for now! We'll finish up this project tomorrow in Part 4.

Want more Azure Functions? Check out our [quickstarts and tutorials](https://docs.microsoft.com/azure/azure-functions/?WT.mc_id=azureapril_devto-blog-cxa)!

---
We'll be posting articles every day in April, so stay tuned or jump ahead and check out more tips and tricks [now](http://azuredev.tips).