How I Designed A Smart Contact-Me Form
Tags: learning, python, serverless, showdev
Published: 2021-04-12 18:49:25

This is not a coding tutorial, but a very high level overview of my implementation. Nonetheless, I’ve shared links to my [github code](https://github.com/rodiwa/contact-form-python) and various references inline. The contact-me form is on my [website](http://rohitdiwakar.com/) (scroll to the end).

## The Problem
I wanted a practical contact-me form for my website so I could be notified when somebody left me a message. I also wanted to reply back to the user so they have my contact info as well.

## The Solution
For notification, I decided to have a [Slack Incoming Webhook](https://api.slack.com/messaging/webhooks). I would create a Slack channel and messages sent to me from my website would show up here. For the reply-back, I decided to send an email to the user only if they shared an existing/ valid email address.
![Solution diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/knh4c80qg93wpj37tw21.png)

## The Brainstorming
- I initially had a NodeJS backend + Mongodb Atlas cluster to save message details (name, email, message) directly from the website. But this data was not of any use really. I just wanted to be notified and send a response back to the user.
- To check if email is existing, python has a good library - [validate_email](https://pypi.org/project/validate_email/). NodeJS didn’t really have a lot of ‘obvious’ options, and some had to be [done manually](https://soshace.com/verifying-an-email-address-without-sending-an-email-in-nodejs/) (by [sending an actual email](https://stackoverflow.com/questions/29272695/node-js-email-existence-check) to verify, which I didn’t want).
- I didn’t want a server running at all times, since this feature would be sparingly used. Azure Functions [was a good choice](https://docs.microsoft.com/en-us/dotnet/architecture/serverless/serverless-design-examples#web-apps-and-apis) as I needed it to run (read as “cost me”) only when triggered by an HTTP request.
- I also wanted to perform steps in sequence, and I wanted a way to store the state of my previous step (is email valid or not) and it made sense to use [Azure Durable Functions](https://tsuyoshiushio.medium.com/durable-functions-101-35aa3919f182#:~:text=Durable%20Functions%20use%20Storage%20Table,among%20the%20orchestrator%20and%20activities.) for this.
- Since Python libraries had a lot of my requirements covered, I chose Python over NodeJS.

## The Implementation
- Setup Azure Functions
  - I started with a quickstart Azure Functions (Python) project on my local VS Code [as given](https://docs.microsoft.com/en-us/azure/azure-functions/durable/quickstart-python-vscode) on the Azure docs. I then published this demo to my Az account, and tested the example using Postman and the Az function URL. I now only had to swap out the examples with my own implementations.
  - Az Durable Functions has [3 types of functions](https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-types-features-overview) (now 4 types) - Client (this kicks off the orchestrator), Orchestrator (manages different tasks or activities), and an Activity (what you’re really trying to do). The quickstart-demo gives you these.
  - Since I had 3 main activities to do, I created 3 Activity functions; one each to -  send slack notification (`notifySlackChannel` file in repo), check if email is valid (`isEmailExists`) and send email (`sendEmail`). VS Code Az Functions plugin has quick-actions to create these. Then plug these new activities into the demo code, replacing the calls to Hello with your new 3 activities.
  - I started by [getting my request body](https://github.com/rodiwa/contact-form-python/blob/master/dfHttpStart/__init__.py#L11) as payload and then [passing this payload](https://github.com/rodiwa/contact-form-python/blob/master/dfHttpStart/__init__.py#L13) `{user, email, message}` into the Az Durable Functions.
- Slack Notification - see [`sendEmail`](https://github.com/rodiwa/contact-form-python/tree/master/sendEmail)
  - Create a Slack account, and create a channel where you want your messages.
  - Create a Slack app, enable and create an [incoming webhook](https://slack.com/intl/en-in/help/articles/115005265063-Incoming-webhooks-for-Slack). You will get a slack URL which you can use to POST messages by sending a payload to it; like { text: “your message comes here” }.
  - I also added formatting to my messages by using Slack’s [Block kit UI](https://api.slack.com/block-kit). It’s pretty neat because all you have to do is send some additional JSON objects (for a button, etc) to the payload data. Rest remains the same. Sweet.
- Check if email is valid - see [`isEmailExists`](https://github.com/rodiwa/contact-form-python/tree/master/isEmailExists)
  - This is a simple python code that checks if the given email exists
- Send email - see [`sendEmail`](https://github.com/rodiwa/contact-form-python/tree/master/sendEmail)
  - Get output of previous operation - `isEmailExists`. If this value is `True`, only then go ahead with this step.
  - This was a simple implementation, almost as given [in their examples](https://dev.mailjet.com/email/guides/send-api-v31/).
- Environment variables
  - Best to not check-in config with code, and to use environment variables instead.
  - Az funcs allow you to add [variables as configuration](https://docs.microsoft.com/en-us/azure/azure-functions/functions-how-to-use-azure-function-app-settings?tabs=portal) which can be used by your code.

## Troubleshoots
- The python smtplib (for sending emails) works fine on local, but when you deploy code to Az functions, [Az does not pass the emails](https://stackoverflow.com/questions/58953123/i-can-not-send-email-from-python-azure-function-smtp-office365-com-587#:~:text=1%20Answer&text=The%20only%20way%20to%20use,provides%20these%20type%20of%20services.&text=So%20the%20e%2Dmails%20sent,as%20SPAM%20by%20mail%20servers.) through. You need to use a [SMTP relay services](https://stackoverflow.com/questions/50059171/not-able-to-connect-to-smtp-from-azure-cloud-service/50075862#50075862) (SendGrid, Mailjet) to get this done on Azure.
- Passing parameters from HTTP request to Az function is not very intuitive.
- Debugging on Az functions after deployment is a pain. Use `logger.info` instead of `print()` (python) or `console.log` (JS).
- The running logs on Az functions are not consistent and do not show/ run at times. Az portal has to be refreshed.
- I had trouble with SendGrid. Looks like [others have too](https://stackoverflow.com/questions/17666161/sending-email-from-azure/55470893#55470893). For me, they asked for way too many details (organization, address, reason to use this service, etc) on account creation and I wanted to be vague since I was only setting up for a dev project. They did not let me use SendGrid and forced me to create a ticket with support to justify how I wanted to use SendGrid for sending emails. Rude. Screw that. [Mailjet](https://www.mailjet.com/) was way easier to set up and their free account gives more emails (Mailjet allows 200, as compared to SendGrids 100 emails per day).