Scrape a Website & Send an E-mail with Azure Functions
Tags: net, azure, azurefunctions, serverless
Published: 2018-06-30 17:10:52

---
title: Scrape a Website & Send an E-mail with Azure Functions
published: true
tags: 
  - .NET
  - Azure
  - Azure Functions
  - Serverless
cover_image: https://thepracticaldev.s3.amazonaws.com/i/wmcy4q7a0lo91lisiguw.png
canonical_url: https://mattferderer.com/scrape-a-website-and-send-an-email-with-azure-functions
---

Serverless Functions are an awesome way to create small tasks that can run on a schedule, by the click of a button or using your voice. We'll create a function that visits a website, collects important data & sends that via an e-mail back to us on a schedule.

This project will use Azure Functions. They are dirt cheap, especially for this project. Like most serverless architectures, you only pay for the server time it takes your function to run.

If you don't have an Azure account, a quick search for "Free Azure Account" should take you to Microsoft's latest offering.

## <svg aria-hidden="true" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>Create an Azure Function

Login to Azure and click to _Create a Resource_. Here you can search for _Functions_ and add a _Function App_.

On the next screen give the app a name, select a resource group, etc. If you're new to this, drop me a line in the comments & I can walk you through these steps. Once done, click _Create_.

Once Azure has the new resources ready, you can either click the pop up that appears or go to _All Resources_ and find the Function App there. The Function App has a lightning bolt icon.

On the next screen, hover over _Functions_ on the left menu. Click the + sign that appears to create our Function.

![Azure Functions Overview Screen.](https://mattferderer.com/scrape-a-website-and-send-an-email-with-azure-functions-1-a5fcd4a6fa8df3255c01472832f3aef8.gif)

Select _Timer_ if you want to do this on a schedule or _Webhook+API_ if you want a URL to send a request to so you can do it on demand. Also make sure C# is selected as the language below.

On the right side, click View Files.

![Click View Files on the right side of the screen.](https://mattferderer.com/scrape-a-website-and-send-an-email-with-azure-functions-2-48c8a11a31647280d9436a0e12eab44f.gif)

Add a new `project.json` file and add the following text. Then click the Save button:

```
{
  "frameworks": {
    "net46":{
      "dependencies": {
        "HtmlAgilityPack": "1.8.2"
      }
    }
  }
}
```

_Note: Currently only [.NET Framework 4.6 is supported on Azure Functions](https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-csharp#using-nuget-packages). .Net Core is available in preview. If at any later time you wish to use .Net Core instead, adjust the framework snippet above. The following code should require minimum changes if any._

For this demo & purposes of legality, we'll scrape [my blog's homepage](https://mattferderer.com). Here's the code I wrote. If you're looking to make multiple asynchronous requests, the comment in the GetLatestArticles function explains how.

```
#r "SendGrid"
using System.Net;
using HtmlAgilityPack;
using SendGrid.Helpers.Mail;

// This is our main method that's called when the function runs. Azure passes the TimerInfo object and a TraceWriter object to output logs. We use C#'s out parameter modifier to work with Azure's Sendgrid e-mail integration using the Mail class.
public static void Run(TimerInfo myTimer, TraceWriter log, out Mail message)
{
    try{
        var latestArticle = GetLatestArticles().Result;
        log.Info(latestArticle);
        message = SendEmail(latestArticle);
    } catch (Exception e) {
        log.Info(e.ToString());
        message = SendEmail(e.ToString());
    }

}

public static async Task<string> GetLatestArticles() {
    var mattsLatestArticle = await GetMattsLatestArticle();
    /**
     * If you want to scrape additional resources with async, 
     * add more functions like above.
     *
     * Then return them like this:
     * return mattsLatestArticle + otherLatestArticle;
     */
    return mattsLatestArticle;
}

public static async Task<string> GetMattsLatestArticle() {
    string website = "https://mattferderer.com";

    HttpClient client = new HttpClient();
    string html = await client.GetStringAsync(website);

    HtmlDocument doc = new HtmlDocument();
    doc.LoadHtml(html); 

    var article = doc.DocumentNode
        .SelectNodes("//a")
        .FirstOrDefault(x => x.Attributes.Contains("class") && x.Attributes["class"].Value.Contains("article-link"));

    var url = article
        .Attributes
        .FirstOrDefault(x => x.Name == "href")
        .Value;

    var title = article
        .SelectSingleNode("//h2")
        .InnerHtml;

    return $"<p><a href=\"{website}{url}\">{title}</a></p>";
}

public static Mail SendEmail(string input) {
        var message = new Mail
    {
        Subject = "Latest Article"
    };

    var personalization = new Personalization();
    personalization.AddTo(new Email("yourEmail@example.com"));

    var content = new Content
    {
        Type = "text/html",
        Value = input
    };
    message.AddContent(content);
    message.AddPersonalization(personalization);
    return message;
}
```

## <svg aria-hidden="true" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>Add SendGrid Integration

Before this will send an e-mail it needs an e-mail service integrated with the Azure account. I recommend [SendGrid](https://sendgrid.com).

[SendGrid](https://sendgrid.com) is easy to integrate with Azure & a has a free developer plan. They're also a great provider in general. For these reasons, they're my go to for fun apps & production apps.

Create a [SendGrid](https://sendgrid.com) account and then go to settings > API Keys. Click create an API Key. Give it a name. Restricted access with `Mail Send` is all we need for permissions. Copy your API key somewhere safe.

Back in Azure, open the Functions Application Settings by clicking the app's name on the left menu. Then click Application Settings near the bottom under Configured features. (See highlighted links below)

![Click Application Settings under Configured features](https://mattferderer.com/scrape-a-website-and-send-an-email-with-azure-functions-3-b415cb520c04975e6c3f28dbef8b5f47.gif)

Scroll down to the Application Settings, above Connection strings and click Add new setting.

Add the key `SendGridKey`. For the value, add the API key Sendgrid gave you. Click save.

Under the functions name on the left, click Integrate & add a new Output.

Select SendGrid from the list of Output options.

In the SendGrid API Key drop down menu on the next screen, select SendGridKey.

Fill out the From address & subject as well if you want defaults for the function. Make sure to Save.

The output settings will now be added to the function.json file that Azure created for you if you did a Timer Trigger app. You can also find the schedule using a Cron pattern in there.

At this point, you should be able to run the Azure Function & have it send you an e-mail. If not, let me know in the comments.

## <svg aria-hidden="true" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>Additional Notes

If you're sending links in the e-mail, as done in the above example, you may find it useful to [turn off SendGrid's "Tracking Settings"](https://sendgrid.com/docs/User_Guide/Settings/tracking.html) feature. This feature changes your links & sometimes breaks them if you don't have this setup right. You can disable it on Sendgrid's Admin screen by going to Settings > Tracking.

You can debug your code locally using [VS Code](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions) or [Visual Studio](https://docs.microsoft.com/en-us/azure/azure-functions/functions-develop-vs).