A Well-Intended, Slightly Naive Adventure
This all started, like many tech digressions, with humble beginnings. I wanted to find if anyone else had used Azure Functions for handling messaging through their static site, and there were several examples. Unfortunately the information had grown stale by the time I found it. The core concept is out there in several repos/blog posts, but there have been changes since then which invalidates a straight fork-and-deploy approach. So I did a bit of my own homework and derived a workable solution. I’ll start with a brief walkthrough - a GIF of an early version of the form in action.
The Function can take up to 20 seconds to respond when initiating from a cold start. So this GIF has been edited as a courtesy. And as much as I’d like to think I’m popular, the likelihood of multiple people clicking “Submit” when a Function App instance is idling is - in a word - remote. Even while testing on an idling Function instance there’s still enough wait time to see the animation “pulse” on screen once or twice. So it’s more to the point that having a bit of action in the page to represent the request/response cycle can indicate to the user that things are happening behind the scenes.
While I provided inline comments for some of the code blocks, there’s still a few bits of context worth mentioning here. Below is the Node-based Function App message binding, key being the nested from element that contains both the name and email of the submitted form. There are several alternatives to this using “personalization” but it’s really unnecessary with the latest API.
There were no examples online that showed this specific binding pattern. So I cobbled this together by looking at SendGrid’s API docs plus the sample message binding in a SendGrid-native template that Microsoft makes available in the Function Apps portal.
I’ve read several posts from first-order front-end developers disparaging inline styles, but in this case I kept it for - you guessed it - visibilty. It may be something I refactor at a later date.
The inline comments provide enough breadcrumbs to follow the logic. There’s a possibility for more verbose error messaging (see my caveat below) but this works well enough to get things moving along.
In the original Wordpress version of this site I used the Pagelines framework, which included SemanticUI. So I could drop in alerts with a class tag and everything just lit up as expected. But that came at a cost, which is as much on Wordpress - but the JS/CSS bulk certainly didn’t help. So while I didn’t want to hand-craft my own CSS I also didn’t want to deal with another bulky framework. So I found the skeleton-alerts CSS library, which fits the bill perfectly.
The icon animation also came from an online resource. Loading CSS provides a concise library to handle a variety of behaviors. I auditioned the heartbeat behavior on a lark, and liked it so much that I decided that it was the keeper of the bunch.
So while I gloss over how well everything works when it’s working, there were a few stumbling blocks that was a mixture of my own cobwebs working with Node and some minor gaps in documentation here and there. Below are two examples of things that cost me a lot more time than they would have if I was a more seasoned front-end developer.
The fault, dear Brutus, is not in our stars…
The first thing that tripped me up was an output binding error message I was seeing when testing in the portal. I had started with an HTTP triggered function template, and added the SendGrid output bindings afterward.
…But in ourselves, that we are underlings
I was in a hurry. That was my first mistake. There were other problems I encountered in troubleshooting that were largely of my own making. My machine is still relatively new and I hadn’t bothered changing my default browser from Microsoft Edge. I would open Firefox or Chrome manually and paste in the local URL when I needed to check the local build on those browsers. In this case that was a strategic mistake. The messages coming back in Edge didn’t give me the detail to show the other mistake I had made - that is - to make sure my CORS settings would allow my localhost as an origin. Once I popped over to Chrome I nearly blew a gasket - both at the lack of verbosity in Edge and in my own impatience for missing this.
When running the test in portal, everything worked fine. The browser is on my desktop so there’s no problem, right? But the origin header for the request from the test in portal is already pre-cleared by the function app defaults.
I had already set up my production and staging endpoints to pass through, but simply forgot to add the localhost origin. And of course I paid almost no attention to the origins that were already listed - which I assume allow the tests in the Funtion App portal to process without issue. I lost more than an hour beating myself up trying to get this to work. Eventually I gave up and simply deployed into my staging instance to run the tests, which was fine - but the find/fix cycle is not nearly as quick as running locally. I suppose if I had configured VSCode for remote debugging I would have found this earlier. That’s what “being in a hurry” gets me…
So now I have Firefox set as my default browser, which is a real acid test for a variety of reasons I’ll delve into with another post.
Above are the settings that allow my localhost to work - your mileage (and localhost declaration pattern/port) may vary. If you’re familiar with serving locally in RStudio/Hugo you’ll know about how ports will change if you happen to trigger more than one server instance. Port 4321 is the intial default - Ctrl/Cmd+Alt+F10 is your friend here.
I really had fun placing the Azure Function App logo into the form as a spinner. That plus the gray overlay gives the page a guided feel that an empty “static” form simly doesn’t convey. The overlay also acts as a mask to prevent multiple clicks of the submit button, which isn’t a real risk but stil nice to keep the interaction model relatively tidy.
I’m a huge fan of serverless, and am really glad to have this as a resource. It’s essentially free - as the basic SendGrid account allows 25,000 free messages per month. I doubt I see 25 messages per month from my site - outside of my own testing. Of course the first paid tier for Azure Function App in the Consumption plan is after 1 million runs, so again it feels like you’re receiving free enterprise-grade resources. And this will also come in handy for processing notifications out of my GitHub Actions pipelines, which is a subject for a later post.
If you’re interested in trying something similar, feel free to fork the repo and give it a try!
Coda - Reception
As usual, there’s one more thing… the email. In my case, I’m routing to a Gmail account. Since this is coming from SendGrid the messages were automatically flagged as possible SPAM. The answer in my case was to create a filter that ensured it wouldn’t get routed to the SPAM folder.
Note that I used my domain name in the subject line as a flag - which is set in the bindings of the index.js of the Function App. The rule both sets the message to always be flagged as important and never marked as SPAM. That way the message is always routed appropriately. So now messages from your site will route to the top of your inbox and all you have to do is click on the “reply” button and type away. Happy emailing!