<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>F# on The Personal Blog of Emily Gorcenski</title><link>https://emilygorcenski.com/tags/f%23/</link><description>Recent content in F# on The Personal Blog of Emily Gorcenski</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Sun, 08 Jan 2023 20:53:40 +0100</lastBuildDate><atom:link href="https://emilygorcenski.com/tags/f%23/index.xml" rel="self" type="application/rss+xml"/><item><title>Posts without posting: building a social media sharing relay with F♯ and Azure Functions, part 4</title><link>https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-4/</link><pubDate>Sun, 08 Jan 2023 20:53:40 +0100</pubDate><guid>https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-4/</guid><image><url>https://emilygorcenski.com/photo-gallery/writing/shortcut1.jpg"</url></image><enclosure url="https://emilygorcenski.com/photo-gallery/writing/shortcut1.jpg" length="" type="image/jpeg"/><media:content url="https://emilygorcenski.com/photo-gallery/writing/shortcut1.jpg" width="850" medium="image" type="image/jpeg"><media:thumbnail url="https://emilygorcenski.com/photo-gallery/writing/shortcut1.jpg" width="600"/></media:content><description>&lt;p&gt;In the first three parts of the series, I set up an Azure Functions endpoint to accept a URL and a comment and share it across various social media sources. The last step of making this a proper workflow is to build the iOS shortcut.&lt;/p&gt;
&lt;p&gt;The last two parts of this series were very long and tech heavy. This part is mercifully shorter and actually quite easy. The goal here is to create a shortcut in iOS to extract the URL and page title from a website I am visiting, prompt me for a comment, and then hit the API endpoint previously set up with an authentication key. This can be done with the built-in iOS Shortcuts app that ships with all current versions of iOS.&lt;/p&gt;
&lt;blockquote&gt;
&lt;h4 id="posts-without-posting-a-series"&gt;Posts without posting, a series&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-1/"&gt;Architecting a relay&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-2/"&gt;Writing the code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-3/"&gt;Configuring Azure Functions with Terraform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-4/"&gt;Setting up an iOS Shortcut&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a very simple seven step process. In the iOS Shortcuts app, let&amp;rsquo;s create a shortcut called &amp;ldquo;Post Link&amp;rdquo;. The steps are as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Get &lt;code&gt;Any&lt;/code&gt; input from &lt;code&gt;Share Sheet&lt;/code&gt; (Sharing -&amp;gt; System -&amp;gt; Share)&lt;/li&gt;
&lt;li&gt;Ask for &lt;code&gt;Text&lt;/code&gt; with &lt;code&gt;Comment&lt;/code&gt; (Scripting -&amp;gt; Notification -&amp;gt; Ask for Input)&lt;/li&gt;
&lt;li&gt;Set variable &lt;code&gt;Tweet&lt;/code&gt; to &lt;code&gt;Provided Input&lt;/code&gt; (Scripting -&amp;gt; Variables -&amp;gt; Set Variable)&lt;/li&gt;
&lt;li&gt;Get &lt;code&gt;Page URL&lt;/code&gt; from &lt;code&gt;Shortcut Input&lt;/code&gt; (Web -&amp;gt; Safari -&amp;gt; Get Details of Safari Web Page)&lt;/li&gt;
&lt;li&gt;Get &lt;code&gt;Name&lt;/code&gt; from &lt;code&gt;Shortcut Input&lt;/code&gt; (Web -&amp;gt; Safari -&amp;gt; Get Details of Safari Web Page)&lt;/li&gt;
&lt;li&gt;Enter URL of endpoint (Web -&amp;gt; URLs -&amp;gt; URL)&lt;/li&gt;
&lt;li&gt;Get Contents of URL (Web -&amp;gt; Web Requests -&amp;gt; Get Contents of URL)&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;Method&lt;/code&gt; to &lt;code&gt;POST&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add a header with &lt;code&gt;key&lt;/code&gt; set to &lt;code&gt;x-functions-key&lt;/code&gt; and &lt;code&gt;text&lt;/code&gt; set to your function key as found in the Azure Function you just set up&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That&amp;rsquo;s it! Here are some images of how mine looks. To use this, whenever I am viewing a website, I can simply open the share tray and click &amp;ldquo;Post Link.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://emilygorcenski.com/photo-gallery/writing/shortcut1.jpg" alt="First page of the shortcut described above as shown in the iOS app"&gt;
&lt;img src="https://emilygorcenski.com/photo-gallery/writing/shortcut2.jpg" alt="Second page of the shortcut described above as shown in the iOS app"&gt;&lt;/p&gt;</description></item><item><title>Posts without posting: building a social media sharing relay with F♯ and Azure Functions, part 3</title><link>https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-3/</link><pubDate>Sat, 31 Dec 2022 04:08:30 +0100</pubDate><guid>https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-3/</guid><description>&lt;p&gt;In the first two parts of the series, I introduced the architecture for building a link sharing relay and then built it with a little bit of F# code. This post covers setting up the infrastructure and deploying the code as an Azure Function.&lt;/p&gt;
&lt;p&gt;Azure functions offer a really nice way to build serverless endpoints. Azure functions are phenomenally flexible, and in the &lt;a href="https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-2/"&gt;previous part&lt;/a&gt; I showed how to set up an F# Azure function with an &lt;code&gt;HttpTrigger&lt;/code&gt;. It runs locally, but to make it useful we need to deploy it to Azure. We&amp;rsquo;ll need to configure not a small amount of infrastructure to do so.&lt;/p&gt;
&lt;blockquote&gt;
&lt;h4 id="posts-without-posting-a-series"&gt;Posts without posting, a series&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-1/"&gt;Architecting a relay&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-2/"&gt;Writing the code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-3/"&gt;Configuring Azure Functions with Terraform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-4/"&gt;Setting up an iOS Shortcut&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;This post will be a little lengthy, but hopefully not as long as the last. I&amp;rsquo;ll use terraform to set things up, but there&amp;rsquo;s one step that doesn&amp;rsquo;t seem to be working. Configuring a Custom Domain for the Azure Function seems not to work, so this will be a small manual step. It&amp;rsquo;s not ideal, but it&amp;rsquo;s only one extra step for managing DNS. Some of my terraform scripts are inherited from previous work, but I&amp;rsquo;ll do my best to present them here as if I am using them for the first time.&lt;/p&gt;
&lt;p&gt;To deploy the function, we&amp;rsquo;ll do the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set up an Azure Resource Group&lt;/li&gt;
&lt;li&gt;Set up Azure Storage for deploying the function code&lt;/li&gt;
&lt;li&gt;Set up Azure Insights for monitoring&lt;/li&gt;
&lt;li&gt;Set up a Key Vault for secrets management&lt;/li&gt;
&lt;li&gt;Set up a Service Plan for the Azure Function&lt;/li&gt;
&lt;li&gt;Set up an Azure Function&lt;/li&gt;
&lt;li&gt;Configure a Key Vault Access Policy for the Azure Function&lt;/li&gt;
&lt;li&gt;Deploy the function&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&amp;rsquo;s get started!&lt;/p&gt;
&lt;h4 id="create-an-azure-resource-group"&gt;Create an Azure Resource Group&lt;/h4&gt;
&lt;p&gt;First, we&amp;rsquo;ll set up a couple variables and data source in terraform.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# variables.tf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;variable&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;domain&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;type&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;default&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;emilygorcenski.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;variable&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;regions&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;type&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; map(&lt;span style="color:#a6e22e"&gt;string&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;default&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;primary&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;germanywestcentral&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;functions-eu&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;northeurope&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;Not every region allows you to deploy Azure functions with a consumption plan (serverless), so I create some options in my variables. Next, we&amp;rsquo;ll set up our terraform provider. I&amp;rsquo;m ignoring the part where I have a remote terraform backend, but I recommend adding that, as well. My versions here are almost surely out of date, but I didn&amp;rsquo;t have time to fix that before writing this post. I&amp;rsquo;m on a schedule here!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# main.tf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;terraform&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;required_providers&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;azurerm&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;source&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;hashicorp/azurerm&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;gt;= 2.91&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;azuread&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;source&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;hashicorp/azuread&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;version&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;~&amp;gt; 2.0.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;Finally, we can add the following to our &lt;code&gt;main.tf&lt;/code&gt; file. Of course, variable names in &lt;code&gt;ALL_CAPS&lt;/code&gt; should be replaced with something that makes sense for you.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;resource&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;azurerm_resource_group&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;rg&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;RESOURCE_GROUP_NAME&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;location&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; var.&lt;span style="color:#a6e22e"&gt;regions&lt;/span&gt;[&lt;span style="color:#e6db74"&gt;&amp;#34;primary&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;
&lt;h4 id="setting-up-azure-storage"&gt;Setting up Azure storage&lt;/h4&gt;
&lt;p&gt;Once we have the resource group set up, we can configure a storage bucket that we can deploy our code into. Let&amp;rsquo;s create a new file, &lt;code&gt;link-sharer.tf&lt;/code&gt;, and put the following block in it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;resource&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;azurerm_storage_account&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;links_app_storage&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;STORAGE_NAME&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resource_group_name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;azurerm_resource_group&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;rg&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;location&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; var.&lt;span style="color:#a6e22e"&gt;regions&lt;/span&gt;[&lt;span style="color:#e6db74"&gt;&amp;#34;functions-eu&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;account_tier&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Standard&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;account_replication_type&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;LRS&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;No further action is needed here.&lt;/p&gt;
&lt;h4 id="configure-azure-insights"&gt;Configure Azure Insights&lt;/h4&gt;
&lt;p&gt;We&amp;rsquo;ll want to monitor our function, and while this step might be a little out of order, that&amp;rsquo;s ok. Let&amp;rsquo;s configure Azure Insights to let us monitor our function once we deploy. In &lt;code&gt;link-sharer.tf&lt;/code&gt;, add:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;resource&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;azurerm_application_insights&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;links_afapp_insights&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;APP_INSIGHTS_NAME&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;location&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; var.&lt;span style="color:#a6e22e"&gt;regions&lt;/span&gt;[&lt;span style="color:#e6db74"&gt;&amp;#34;functions-eu&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resource_group_name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;azurerm_resource_group&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;rg&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;application_type&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;web&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="set-up-a-key-vault"&gt;Set up a Key Vault&lt;/h4&gt;
&lt;p&gt;This step is a little more difficult. Let&amp;rsquo;s create a new file, &lt;code&gt;key-vault.tf&lt;/code&gt;. We&amp;rsquo;ll start by configuring the Key Vault.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;resource&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;azurerm_key_vault&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;links_afapp_keyvault&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;KEYVAULT_NAME&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;location&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;azurerm_resource_group&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;rg&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;location&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resource_group_name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;azurerm_resource_group&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;rg&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;tenant_id&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; data.&lt;span style="color:#a6e22e"&gt;azurerm_client_config&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;current&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;tenant_id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;sku_name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;standard&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;soft_delete_retention_days&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;access_policy&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;tenant_id&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; data.&lt;span style="color:#a6e22e"&gt;azurerm_client_config&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;current&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;tenant_id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;object_id&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; data.&lt;span style="color:#a6e22e"&gt;external&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;account_info&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;result&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;object_id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;certificate_permissions&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Create&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Delete&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;DeleteIssuers&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Get&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;GetIssuers&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Import&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;List&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;ListIssuers&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;ManageContacts&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;ManageIssuers&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Purge&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;SetIssuers&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Update&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;key_permissions&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Backup&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Create&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Decrypt&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Delete&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Encrypt&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Get&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Import&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;List&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Purge&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Recover&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Restore&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Sign&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;UnwrapKey&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Update&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Verify&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;WrapKey&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;secret_permissions&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Backup&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Delete&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Get&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;List&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Purge&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Recover&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Restore&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Set&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;This will allow you to set up your Key Vault and gives your user account full permissions to manage keys. I think this is probably suboptimal, but I haven&amp;rsquo;t really dove into finding a better way to do this yet.&lt;/p&gt;
&lt;p&gt;Next, in your &lt;code&gt;main.tf&lt;/code&gt; file, put the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;provider&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;azurerm&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;features&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;key_vault&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;purge_soft_delete_on_destroy&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;This is needed because &lt;a href="https://github.com/hashicorp/terraform-provider-azurerm/pull/10088"&gt;as of December 2020&lt;/a&gt;, all Key Vaults need to have &amp;ldquo;Soft Delete on Destroy&amp;rdquo; enabled.&lt;/p&gt;
&lt;p&gt;Also in &lt;code&gt;main.tf&lt;/code&gt;, let&amp;rsquo;s load our local data from our JSON for the secrets. I suppose we could also be reading these from environment variables, too, but this is how I do it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;locals&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;twitter_data&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; jsondecode(file(&lt;span style="color:#e6db74"&gt;&amp;#34;PATH/TO/TWITTER/CREDS/twitter-api.json&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;mastodon_data&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; jsondecode(file(&lt;span style="color:#e6db74"&gt;&amp;#34;PATH/TO/MASTODON/CREDS/mastodon-api.json&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;google_data&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; jsondecode(file(&lt;span style="color:#e6db74"&gt;&amp;#34;PATH/TO/GOOGLE/CREDS/google-key.json&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;Now, we can use this data in our &lt;code&gt;key-vault.tf&lt;/code&gt; file to set up the secrets.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;resource&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;azurerm_key_vault_secret&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;google-sheets-key&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;GOOGLE-SERVICE-ACCOUNT-CREDENTIAL&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; base64encode(&lt;span style="color:#a6e22e"&gt;local&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;google_data&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;GOOGLE_SERVICE_ACCOUNT_CREDENTIAL&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;key_vault_id&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;azurerm_key_vault&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;links_afapp_keyvault&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;resource&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;azurerm_key_vault_secret&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;google-sheet-id&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;GOOGLE-SHEET-ID&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;local&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;google_data&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;GOOGLE_SHEET_ID&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;key_vault_id&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;azurerm_key_vault&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;links_afapp_keyvault&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;resource&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;azurerm_key_vault_secret&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;twitter-consumer-key&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;twitter-consumer-key&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;local&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;twitter_data&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;TWITTER_CONSUMER_KEY&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;key_vault_id&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;azurerm_key_vault&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;links_afapp_keyvault&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;resource&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;azurerm_key_vault_secret&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;twitter-consumer-key-secret&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;twitter-consumer-key-secret&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;local&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;twitter_data&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;TWITTER_CONSUMER_KEY_SECRET&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;key_vault_id&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;azurerm_key_vault&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;links_afapp_keyvault&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;resource&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;azurerm_key_vault_secret&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;twitter-access-token&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;twitter-access-token&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;local&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;twitter_data&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;TWITTER_ACCESS_TOKEN&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;key_vault_id&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;azurerm_key_vault&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;links_afapp_keyvault&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;resource&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;azurerm_key_vault_secret&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;twitter-access-token-secret&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;twitter-access-token-secret&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;local&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;twitter_data&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;TWITTER_ACCESS_TOKEN_SECRET&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;key_vault_id&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;azurerm_key_vault&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;links_afapp_keyvault&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;resource&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;azurerm_key_vault_secret&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;mastodon-server&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;mastodon-server&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;local&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;mastodon_data&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;MASTODON_SERVER&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;key_vault_id&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;azurerm_key_vault&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;links_afapp_keyvault&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;resource&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;azurerm_key_vault_secret&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;mastodon-access-token&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;mastodon-access-token&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;local&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;mastodon_data&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;MASTODON_ACCESS_TOKEN&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;key_vault_id&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;azurerm_key_vault&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;links_afapp_keyvault&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;That&amp;rsquo;s it for now. We&amp;rsquo;ll come back to this.&lt;/p&gt;
&lt;h4 id="set-up-a-service-plan"&gt;Set up a Service Plan&lt;/h4&gt;
&lt;p&gt;We need to set up a Service Plan where we specify our compute SKU and other details for the Azure function. This is easy. We want to use a &lt;code&gt;Y1&lt;/code&gt; SKU on a &lt;code&gt;Windows&lt;/code&gt; OS. This is a consumption (serverless) SKU where you pay for what you use.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;resource&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;azurerm_service_plan&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;links_afapp_service_plan&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;SERVICE_PLAN_NAME&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resource_group_name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;azurerm_resource_group&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;rg&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;location&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; var.&lt;span style="color:#a6e22e"&gt;regions&lt;/span&gt;[&lt;span style="color:#e6db74"&gt;&amp;#34;functions-eu&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;sku_name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Y1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;os_type&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Windows&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="set-up-the-azure-function"&gt;Set up the Azure Function&lt;/h4&gt;
&lt;p&gt;With all this done, we can finally set up the Azure Function. In your &lt;code&gt;main.tf&lt;/code&gt;, add the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;resource&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;azurerm_windows_function_app&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;links_app&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;AZURE_FUNCTION_NAME&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resource_group_name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;azurerm_resource_group&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;rg&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;location&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; var.&lt;span style="color:#a6e22e"&gt;regions&lt;/span&gt;[&lt;span style="color:#e6db74"&gt;&amp;#34;functions-eu&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;service_plan_id&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;azurerm_service_plan&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;links_afapp_service_plan&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;storage_account_name&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;azurerm_storage_account&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;links_app_storage&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;storage_account_access_key&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;azurerm_storage_account&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;links_app_storage&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;primary_access_key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;identity&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;type&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;SystemAssigned&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;site_config&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;use_32_bit_worker&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;app_settings&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;FUNCTIONS_WORKER_RUNTIME&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;dotnet&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;AzureWebJobsDisableHomepage&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;GOOGLE_SERVICE_ACCOUNT_CREDENTIAL&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;@Microsoft.KeyVault(SecretUri=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;azurerm_key_vault_secret&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;google&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;sheets&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;)&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;GOOGLE_SHEET_ID&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;@Microsoft.KeyVault(SecretUri=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;azurerm_key_vault_secret&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;google&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;sheet&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;)&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;APPINSIGHTS_INSTRUMENTATIONKEY&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;azurerm_application_insights&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;il_afapp_insights&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;instrumentation_key&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;TWITTER_CONSUMER_KEY&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;@Microsoft.KeyVault(SecretUri=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;azurerm_key_vault_secret&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;twitter&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;consumer&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;)&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;TWITTER_CONSUMER_KEY_SECRET&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;@Microsoft.KeyVault(SecretUri=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;azurerm_key_vault_secret&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;twitter&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;consumer&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;secret&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;)&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;TWITTER_ACCESS_TOKEN&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;@Microsoft.KeyVault(SecretUri=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;azurerm_key_vault_secret&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;twitter&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;access&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;token&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;)&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;TWITTER_ACCESS_TOKEN_SECRET&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;@Microsoft.KeyVault(SecretUri=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;azurerm_key_vault_secret&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;twitter&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;access&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;token&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;secret&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;)&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;MASTODON_SERVER&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;@Microsoft.KeyVault(SecretUri=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;azurerm_key_vault_secret&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;mastodon&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;server&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;)&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;MASTODON_ACCESS_TOKEN&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;@Microsoft.KeyVault(SecretUri=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;azurerm_key_vault_secret&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;mastodon&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;access&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;token&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;)&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The app settings here are very important, this is how we load secrets in from the Azure Key Vault. This should automatically load in the secrets. However, in my experience, if I change a secret, it does not load the latest version. I&amp;rsquo;ll figure that out later.&lt;/p&gt;
&lt;p&gt;Before we can make this work, however, we need to let the Function App read from the Key Vault.&lt;/p&gt;
&lt;h4 id="read-secrets-from-the-key-vault"&gt;Read secrets from the Key Vault&lt;/h4&gt;
&lt;p&gt;In your &lt;code&gt;main.tf&lt;/code&gt;, add the following at the end.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;resource&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;azurerm_key_vault_access_policy&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;il-managed-id-policy-eu&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;key_vault_id&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;azurerm_key_vault&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;links_afapp_keyvault&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;tenant_id&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;azurerm_windows_function_app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;links_app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;identity&lt;/span&gt;.&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;tenant_id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;object_id&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;azurerm_windows_function_app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;links_app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;identity&lt;/span&gt;.&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;principal_id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;secret_permissions&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Get&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;Sometimes, on re-running terraform, this does not stick. I don&amp;rsquo;t know why. Sometimes I have to manually find the Principal ID, which is under &amp;ldquo;Identity&amp;rdquo; in the left nav in the Function App, and then copy the GUID into the &amp;ldquo;Access Policies&amp;rdquo; section within the Key Vault.&lt;/p&gt;
&lt;h4 id="set-up-dns"&gt;Set up DNS&lt;/h4&gt;
&lt;p&gt;I can&amp;rsquo;t terraform this, perhaps because of lack of supported functionality, but in the Function App you can configure a custom CNAME domain under &amp;ldquo;Custom Domains.&amp;rdquo; Simply copy the relevant information to your DNS provider.&lt;/p&gt;
&lt;h4 id="deploy-the-function"&gt;Deploy the function&lt;/h4&gt;
&lt;p&gt;If the terraform works, then go ahead and apply the changes to your cloud, and let&amp;rsquo;s get ready to deploy the app! This is very simple. Navigate to your folder and type &lt;code&gt;func azure functionapp publish AZURE_FUNCTION_NAME&lt;/code&gt;, replacing the last bit with your function name, of course.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll want to go to &amp;ldquo;Configuration&amp;rdquo; in your Function App to see that secrets are being properly read. If so, great! Go ahead and send a cURL request and see how it works!&lt;/p&gt;
&lt;p&gt;If it doesn&amp;rsquo;t, &lt;a href="mailto:ejgorcenski+efg@gmail.com"&gt;email me&lt;/a&gt; and I will try to help. I probably missed something or made a mistake, because what I present here is a bit modified from my own setup.&lt;/p&gt;
&lt;p&gt;Thanks for following along. Next up, integrating with an iOS shortcut!&lt;/p&gt;</description></item><item><title>Posts without posting: building a social media sharing relay with F♯ and Azure Functions, part 2</title><link>https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-2/</link><pubDate>Tue, 27 Dec 2022 18:52:54 +0100</pubDate><guid>https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-2/</guid><image><url>https://emilygorcenski.com/photo-gallery/writing/railway-posting.jpg"</url></image><enclosure url="https://emilygorcenski.com/photo-gallery/writing/railway-posting.jpg" length="" type="image/jpeg"/><media:content url="https://emilygorcenski.com/photo-gallery/writing/railway-posting.jpg" width="850" medium="image" type="image/jpeg"><media:thumbnail url="https://emilygorcenski.com/photo-gallery/writing/railway-posting.jpg" width="600"/></media:content><description>&lt;p&gt;In the first part of the series, I introduced the architecture for building a link sharing relay. This post covers the actual code needed to make that relay work. I&amp;rsquo;m writing in F#, a beautiful language.&lt;/p&gt;
&lt;h1 id="part-2-writing-the-code"&gt;Part 2: Writing the code&lt;/h1&gt;
&lt;p&gt;Azure functions provide a simple environment to deploy code, requiring a minimum of development overhead. Nevertheless, Azure Functions are not low-code or no-code solutions. We will need to write some code. For this project, I chose F#, so that I could develop my skills in the language and learn a bit more about functional programming. I also wanted to try out the &lt;a href="https://fsharpforfunandprofit.com/posts/recipe-part2/"&gt;Railway Pattern&lt;/a&gt;, which absolutely blew my mind when I first read about it. It&amp;rsquo;s an awesome way to write code, and I can&amp;rsquo;t say I&amp;rsquo;ve mastered it, but I wanted to try it out.&lt;/p&gt;
&lt;blockquote&gt;
&lt;h4 id="posts-without-posting-a-series"&gt;Posts without posting, a series&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-1/"&gt;Architecting a relay&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-2/"&gt;Writing the code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-3/"&gt;Configuring Azure Functions with Terraform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-4/"&gt;Setting up an iOS Shortcut&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;To get started, you&amp;rsquo;re going to need the &lt;a href="https://dotnet.microsoft.com/en-us/download/dotnet/6.0"&gt;.Net 6.0 runtime&lt;/a&gt; and the &lt;a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=v4%2Cwindows%2Ccsharp%2Cportal%2Cbash#install-the-azure-functions-core-tools"&gt;Azure Functions Core Tools&lt;/a&gt;. I strongly recommend using VSCode with the &lt;a href="https://ionide.io/"&gt;Ionide&lt;/a&gt; extension. I&amp;rsquo;m developing on Ubuntu 20.04 LTS on Windows System Linux (WSL) 2, but you should be able to do this on Mac, Linux, or Windows, if you prefer.&lt;/p&gt;
&lt;p&gt;This will be a long post, so I&amp;rsquo;ll try to break it down for you. We&amp;rsquo;ll do the following steps.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set up an Azure Function and familiarize with some key files and configurations&lt;/li&gt;
&lt;li&gt;Build the &lt;code&gt;run&lt;/code&gt; function and configure it to accept &lt;code&gt;POST&lt;/code&gt; requests&lt;/li&gt;
&lt;li&gt;Introduce the railway pattern and how to adapt it for async programming&lt;/li&gt;
&lt;li&gt;Integrate external services&lt;/li&gt;
&lt;li&gt;Test locally&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="step-1-set-up-an-azure-function"&gt;Step 1: Set up an Azure Function&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s begin at the beginning. We&amp;rsquo;re going to set up an azure function. You can do this wherever you&amp;rsquo;d like, but I did it in my static site repository. This might have been a mistake, so I&amp;rsquo;ll recommend you do this in its own folder. You won&amp;rsquo;t have to create it, the tools should set everything up for you. Assuming you&amp;rsquo;ve installed the Azure Functions Core Tools linked above, simply type&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;func init &lt;span style="color:#f92672"&gt;[&lt;/span&gt;PROJECT NAME&lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;Replace &lt;code&gt;[PROJECT NAME]&lt;/code&gt; with your project&amp;rsquo;s name. This should prompt you to select a worker runtime:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Select a number &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; worker runtime:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;1. dotnet
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2. dotnet &lt;span style="color:#f92672"&gt;(&lt;/span&gt;isolated process&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;3. node
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;4. python
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;5. powershell
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;6. custom
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Choose option:
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;Choose Option 1, for &lt;code&gt;dotnet&lt;/code&gt;. Option 2 might resolve the issue I was having with .Net 7.0, but I haven&amp;rsquo;t tested this yet, so let&amp;rsquo;s stick with what&amp;rsquo;s known and easy. You&amp;rsquo;ll next be prompted to pick a language. Choose F#, if you dare.&lt;/p&gt;
&lt;p&gt;This should create a project folder for you, initialize it as a git repository, create a &lt;code&gt;.gitignore&lt;/code&gt;, and create a handful of files: &lt;code&gt;[PROJECT NAME].csproj&lt;/code&gt;, &lt;code&gt;host.json&lt;/code&gt;, &lt;code&gt;local.settings.json&lt;/code&gt;, and a couple of directories.&lt;/p&gt;
&lt;p&gt;Navigate to this file, and let&amp;rsquo;s initialize a new function. Type &lt;code&gt;func new -n [FUNCTION NAME] -l F#&lt;/code&gt; to create a new function &lt;code&gt;[FUNCTION NAME]&lt;/code&gt;. This can be the same as your project name, but need not be. This is because a single function app deployment can have multiple endpoints, each pointing to a different function. When prompted, choose &lt;code&gt;HttpTrigger&lt;/code&gt;. Running this command should add a couple of new files: &lt;code&gt;[FUNCTION NAME].fs&lt;/code&gt;, &lt;code&gt;metadata.json&lt;/code&gt;, and &lt;code&gt;function.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s go ahead and rename &lt;code&gt;[PROJECT NAME].csproj&lt;/code&gt; to &lt;code&gt;[PROJECT NAME].fsproj&lt;/code&gt;. I don&amp;rsquo;t know that it matters, but I feel a little weird calling an F# project like a C# project.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s open the &lt;code&gt;.fsproj&lt;/code&gt; file, which should have some XML inside. Find the block that says&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;None&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Update=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;host.json&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;CopyToOutputDirectory&amp;gt;&lt;/span&gt;PreserveNewest&lt;span style="color:#f92672"&gt;&amp;lt;/CopyToOutputDirectory&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/None&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;None&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Update=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;local.settings.json&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;CopyToOutputDirectory&amp;gt;&lt;/span&gt;PreserveNewest&lt;span style="color:#f92672"&gt;&amp;lt;/CopyToOutputDirectory&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;CopyToPublishDirectory&amp;gt;&lt;/span&gt;Never&lt;span style="color:#f92672"&gt;&amp;lt;/CopyToPublishDirectory&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/None&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;We need to do some small changes here. Update the block as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Compile&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;[FUNCTION NAME].fs&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;None&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;host.json&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;CopyToOutputDirectory&amp;gt;&lt;/span&gt;PreserveNewest&lt;span style="color:#f92672"&gt;&amp;lt;/CopyToOutputDirectory&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/None&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;None&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;local.settings.json&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Exists(&amp;#39;local.settings.json&amp;#39;)&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;CopyToOutputDirectory&amp;gt;&lt;/span&gt;PreserveNewest&lt;span style="color:#f92672"&gt;&amp;lt;/CopyToOutputDirectory&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;CopyToPublishDirectory&amp;gt;&lt;/span&gt;Never&lt;span style="color:#f92672"&gt;&amp;lt;/CopyToPublishDirectory&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/None&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;The first change ensures that we compile our F# code and include it in the build. The second changes involve modifying the &lt;code&gt;Update&lt;/code&gt; attribute to be &lt;code&gt;Include&lt;/code&gt;. This is necessary for local testing. Finally, we add a &lt;code&gt;Condition&lt;/code&gt; attribute to further simplify local testing.&lt;/p&gt;
&lt;p&gt;Open your &lt;code&gt;.fsproj&lt;/code&gt; file, and in the second &lt;code&gt;&amp;lt;ItemGroup&amp;gt;&lt;/code&gt; block, just before &lt;code&gt;&amp;lt;None Include=&amp;quot;host.json&amp;quot;&amp;gt;&lt;/code&gt; add the following snippet: &lt;code&gt;&amp;lt;Compile Include=[FUNCTION NAME].fs&amp;quot; /&amp;gt;&lt;/code&gt;. This ensures that your code is compiled and built. You&amp;rsquo;ll need to do this for every file that you add.&lt;/p&gt;
&lt;p&gt;In VSCode (or your preferred IDE), open &lt;code&gt;[FUNCTION NAME].fs&lt;/code&gt;. It should have an autogenerated template that looks something like this. From now on, I&amp;rsquo;ll be using &lt;code&gt;LinkSharer&lt;/code&gt; as my project name and function name.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fsharp" data-lang="fsharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; LinkSharer
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;open&lt;/span&gt; System
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;open&lt;/span&gt; System.IO
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;open&lt;/span&gt; Microsoft.AspNetCore.Mvc
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;open&lt;/span&gt; Microsoft.Azure.WebJobs
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;open&lt;/span&gt; Microsoft.Azure.WebJobs.Extensions.Http
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;open&lt;/span&gt; Microsoft.AspNetCore.Http
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;open&lt;/span&gt; Newtonsoft.Json
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;open&lt;/span&gt; Microsoft.Extensions.Logging
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;module&lt;/span&gt; LinkSharer &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Define a nullable container to deserialize into.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;[&amp;lt;&lt;/span&gt;AllowNullLiteral&lt;span style="color:#f92672"&gt;&amp;gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NameContainer&lt;/span&gt;() &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;member&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;val&lt;/span&gt; Name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; get&lt;span style="color:#f92672"&gt;,&lt;/span&gt; set
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// For convenience, it&amp;#39;s better to have a central place for the literal.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;[&amp;lt;&lt;/span&gt;Literal&lt;span style="color:#f92672"&gt;&amp;gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; Name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;[&amp;lt;&lt;/span&gt;FunctionName&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;LinkSharer&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&amp;gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; run &lt;span style="color:#f92672"&gt;([&amp;lt;&lt;/span&gt;HttpTrigger&lt;span style="color:#f92672"&gt;(&lt;/span&gt;AuthorizationLevel.Function&lt;span style="color:#f92672"&gt;,&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;get&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;,&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;post&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;,&lt;/span&gt; Route &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&amp;gt;]&lt;/span&gt;req&lt;span style="color:#f92672"&gt;:&lt;/span&gt; HttpRequest&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;log&lt;span style="color:#f92672"&gt;:&lt;/span&gt; ILogger&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; async &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; log&lt;span style="color:#f92672"&gt;.&lt;/span&gt;LogInformation&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;F# HTTP trigger function processed a request.&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; nameOpt &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; req&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Query&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ContainsKey&lt;span style="color:#f92672"&gt;(&lt;/span&gt;Name&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Some&lt;span style="color:#f92672"&gt;(&lt;/span&gt;req&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Query&lt;span style="color:#f92672"&gt;.[&lt;/span&gt;Name&lt;span style="color:#f92672"&gt;].[&lt;/span&gt;0&lt;span style="color:#f92672"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; None
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; stream &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; StreamReader&lt;span style="color:#f92672"&gt;(&lt;/span&gt;req&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Body&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let!&lt;/span&gt; reqBody &lt;span style="color:#f92672"&gt;=&lt;/span&gt; stream&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ReadToEndAsync() &lt;span style="color:#f92672"&gt;|&amp;gt;&lt;/span&gt; Async.AwaitTask
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; data &lt;span style="color:#f92672"&gt;=&lt;/span&gt; JsonConvert.DeserializeObject&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;NameContainer&lt;span style="color:#f92672"&gt;&amp;gt;(&lt;/span&gt;reqBody&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; name &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; nameOpt &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; Some n &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; n
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; None &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; data &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; nc &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; nc&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Name
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; responseMessage &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;String.IsNullOrWhiteSpace&lt;span style="color:#f92672"&gt;(&lt;/span&gt;name&lt;span style="color:#f92672"&gt;))&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Hello, &amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; name &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;. This HTTP triggered function executed successfully.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; OkObjectResult&lt;span style="color:#f92672"&gt;(&lt;/span&gt;responseMessage&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&amp;gt;&lt;/span&gt; IActionResult
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&amp;gt;&lt;/span&gt; Async.StartAsTask
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is boilerplate code and it should run locally. Fun &lt;code&gt;func host start&lt;/code&gt; and try sending a request with &lt;code&gt;curl&lt;/code&gt; to the URL that it displays, typically something like &lt;code&gt;curl http://localhost:7071/api/FUNCTION NAME&lt;/code&gt;. You should see a message like, &lt;code&gt;This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.&lt;/code&gt; If so, congratulations! We&amp;rsquo;ve configured our F# function, let&amp;rsquo;s move onto the next step.&lt;/p&gt;
&lt;h3 id="step-2-build-the-run-function"&gt;Step 2: Build the &lt;code&gt;run&lt;/code&gt; function&lt;/h3&gt;
&lt;p&gt;To build our link sharer, we&amp;rsquo;re going to strip out a lot of the boilerplate and cruft. This is actually going to be very simple. We&amp;rsquo;ll start with the &lt;code&gt;run&lt;/code&gt; function. In your &lt;code&gt;LinkSharer.fs&lt;/code&gt; file, let&amp;rsquo;s modify it as follows.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fsharp" data-lang="fsharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&amp;lt;&lt;/span&gt;FunctionName&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;LinkSharer&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&amp;gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; run &lt;span style="color:#f92672"&gt;([&amp;lt;&lt;/span&gt;HttpTrigger&lt;span style="color:#f92672"&gt;(&lt;/span&gt;AuthorizationLevel.Function&lt;span style="color:#f92672"&gt;,&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;post&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;,&lt;/span&gt; Route &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&amp;gt;]&lt;/span&gt;req&lt;span style="color:#f92672"&gt;:&lt;/span&gt; HttpRequest&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;log&lt;span style="color:#f92672"&gt;:&lt;/span&gt; ILogger&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; async &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; log&lt;span style="color:#f92672"&gt;.&lt;/span&gt;LogInformation&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;F# HTTP trigger function processed a request.&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt;&lt;span style="color:#f92672"&gt;!&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;data&lt;span style="color:#f92672"&gt;:&lt;/span&gt; LinkData&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; getPostFromReq req
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; tweet&lt;span style="color:#f92672"&gt;:&lt;/span&gt; Async&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;ID&lt;span style="color:#f92672"&gt;,&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; getTwitterClient
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;@&lt;/span&gt; writeTweet data
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; sheet&lt;span style="color:#f92672"&gt;:&lt;/span&gt; Async&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;ID&lt;span style="color:#f92672"&gt;,&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; getSheetService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;@&lt;/span&gt; writeToGoogleSheet data
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; toot&lt;span style="color:#f92672"&gt;:&lt;/span&gt; Async&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;ID&lt;span style="color:#f92672"&gt;,&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; getMastodonClient
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;@&lt;/span&gt; writeToot data
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt;&lt;span style="color:#f92672"&gt;!&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;result&lt;span style="color:#f92672"&gt;:&lt;/span&gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;ID&lt;span style="color:#f92672"&gt;,&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tweet
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;==&lt;/span&gt; toot
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;==&lt;/span&gt; sheet
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; result &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; Success s &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; OkObjectResult&lt;span style="color:#f92672"&gt;(&lt;/span&gt;s&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&amp;gt;&lt;/span&gt; IActionResult
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; Failure f &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; StatusCodeResult&lt;span style="color:#f92672"&gt;(&lt;/span&gt;500&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&amp;gt;&lt;/span&gt; IActionResult
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&amp;gt;&lt;/span&gt; Async.StartAsTask
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;This is actually shockingly simple. First, I&amp;rsquo;ve stripped off &lt;code&gt;get&lt;/code&gt; from the function call, as this will be a &lt;code&gt;POST&lt;/code&gt;-only endpoint. Notice &lt;code&gt;AuthorizationLevel.Function&lt;/code&gt;, this forces us to authenticate to use the API. We don&amp;rsquo;t want randos sharing to our social media!&lt;/p&gt;
&lt;p&gt;Next, we have &lt;code&gt;let! (data: LinkData) = getPostFromReq req&lt;/code&gt;. This takes the JSON payload attached to the request and processes it with a function called &lt;code&gt;getPostFromReq&lt;/code&gt;. Essentially, all this does is take the request data and format it using an F# Record type. Let&amp;rsquo;s create that code now. Above the &lt;code&gt;run&lt;/code&gt; method, add the following code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fsharp" data-lang="fsharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;LinkData&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; url&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; title&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; comment&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; getPostFromReq &lt;span style="color:#f92672"&gt;(&lt;/span&gt;req&lt;span style="color:#f92672"&gt;:&lt;/span&gt; HttpRequest&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; async &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; stream&lt;span style="color:#f92672"&gt;:&lt;/span&gt; StreamReader &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; StreamReader&lt;span style="color:#f92672"&gt;(&lt;/span&gt;req&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Body&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt;&lt;span style="color:#f92672"&gt;!&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;reqBody&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; stream&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ReadToEndAsync() &lt;span style="color:#f92672"&gt;|&amp;gt;&lt;/span&gt; Async.AwaitTask
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; JsonConvert.DeserializeObject&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;LinkData&lt;span style="color:#f92672"&gt;&amp;gt;(&lt;/span&gt;reqBody&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;We probably don&amp;rsquo;t need the &lt;code&gt;async&lt;/code&gt; here, but let&amp;rsquo;s use it. What this does is reads the JSON data in the payload and deserializes it to a &lt;code&gt;LinkData&lt;/code&gt; type that we can use easily elsewhere in the code. This is the easy part. Let&amp;rsquo;s look now to the end of the function, where things start to get interesting.&lt;/p&gt;
&lt;h3 id="step-3-introducing-the-railway-pattern"&gt;Step 3: Introducing the railway pattern&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s skip over some code and look at the final block, because this is where things get interesting.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fsharp" data-lang="fsharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt;&lt;span style="color:#f92672"&gt;!&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;result&lt;span style="color:#f92672"&gt;:&lt;/span&gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;ID&lt;span style="color:#f92672"&gt;,&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tweet
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;==&lt;/span&gt; toot
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;==&lt;/span&gt; sheet
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; result &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; Success s &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; OkObjectResult&lt;span style="color:#f92672"&gt;(&lt;/span&gt;s&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&amp;gt;&lt;/span&gt; IActionResult
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; Failure f &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; StatusCodeResult&lt;span style="color:#f92672"&gt;(&lt;/span&gt;500&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&amp;gt;&lt;/span&gt; IActionResult
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;The &lt;code&gt;&amp;gt;&amp;gt;==&lt;/code&gt; operator isn&amp;rsquo;t standard F# syntax. I had to define this operation. This &lt;em&gt;kind of&lt;/em&gt; comes from the &lt;a href="https://fsharpforfunandprofit.com/posts/recipe-part2/#an-implementation-for-parallel-validation"&gt;Railway Oriented Programming blog post&lt;/a&gt;, which introduces &lt;code&gt;&amp;amp;&amp;amp;&amp;amp;&lt;/code&gt; as a parallel operator. I won&amp;rsquo;t explain this all here, I encourage you to read the post and absorb it. It&amp;rsquo;s brilliant. However, the methods presented in the blog post don&amp;rsquo;t work as-is for &lt;code&gt;async&lt;/code&gt; programming. I had to modify them. Using a &lt;a href="https://codereview.stackexchange.com/questions/222235/async-in-railway-oriented-programming-in-f"&gt;Code Review Stackexchange post&lt;/a&gt; as inspiration, I defined the &lt;code&gt;&amp;gt;&amp;gt;==&lt;/code&gt; operator to do async parallel railway switches, because with the Fira Code ligatures I use, it looks nicely parallel.&lt;/p&gt;
&lt;p&gt;To make this work at all, I have to introduce a couple helper functions and types.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fsharp" data-lang="fsharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Result&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;&amp;#39;&lt;/span&gt;TSuccess&lt;span style="color:#f92672"&gt;,&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;&amp;#39;&lt;/span&gt;TFailure&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; Success &lt;span style="color:#66d9ef"&gt;of&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;&amp;#39;&lt;/span&gt;TSuccess
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; Failure &lt;span style="color:#66d9ef"&gt;of&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;&amp;#39;&lt;/span&gt;TFailure
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ID&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt; id &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; bindAsync&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;&amp;#39;&lt;/span&gt;t&lt;span style="color:#f92672"&gt;,&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;&amp;#39;&lt;/span&gt;s&lt;span style="color:#f92672"&gt;,&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;&amp;#39;&lt;/span&gt;terr&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;binder&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;&amp;#39;&lt;/span&gt;t &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; Async&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;&amp;#39;&lt;/span&gt;s&lt;span style="color:#f92672"&gt;,&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;&amp;#39;&lt;/span&gt;terr&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;result&lt;span style="color:#f92672"&gt;:&lt;/span&gt;Async&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;&amp;#39;&lt;/span&gt;t&lt;span style="color:#f92672"&gt;,&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;&amp;#39;&lt;/span&gt;terr&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; Async&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;&amp;#39;&lt;/span&gt;s&lt;span style="color:#f92672"&gt;,&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;&amp;#39;&lt;/span&gt;terr&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; async &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let!&lt;/span&gt; res &lt;span style="color:#f92672"&gt;=&lt;/span&gt; result
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; res &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; Success s &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;&lt;span style="color:#f92672"&gt;!&lt;/span&gt; binder s
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; Failure f &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Failure f
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; plusAsync addSuccess addFailure &lt;span style="color:#f92672"&gt;(&lt;/span&gt;switch1 &lt;span style="color:#f92672"&gt;:&lt;/span&gt; Async&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;&amp;#39;&lt;/span&gt;s&lt;span style="color:#f92672"&gt;,&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;&amp;#39;&lt;/span&gt;terr&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;switch2 &lt;span style="color:#f92672"&gt;:&lt;/span&gt; Async&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;&amp;#39;&lt;/span&gt;s&lt;span style="color:#f92672"&gt;,&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;&amp;#39;&lt;/span&gt;terr&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; async &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let!&lt;/span&gt; res1 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; switch1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let!&lt;/span&gt; res2 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; switch2
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;res1&lt;span style="color:#f92672"&gt;),(&lt;/span&gt;res2&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; Success s1&lt;span style="color:#f92672"&gt;,&lt;/span&gt;Success s2 &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; Success &lt;span style="color:#f92672"&gt;(&lt;/span&gt;addSuccess s1 s2&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; Failure f1&lt;span style="color:#f92672"&gt;,&lt;/span&gt;Success &lt;span style="color:#f92672"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; Failure f1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; Success &lt;span style="color:#f92672"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;,&lt;/span&gt;Failure f2 &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; Failure f2
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; Failure f1&lt;span style="color:#f92672"&gt;,&lt;/span&gt;Failure f2 &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; Failure &lt;span style="color:#f92672"&gt;(&lt;/span&gt;addFailure f1 f2&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&amp;gt;&amp;gt;@)&lt;/span&gt; twoTrackInput switchFunction &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; bindAsync switchFunction twoTrackInput
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&amp;gt;&amp;gt;==)&lt;/span&gt; v1 v2 &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; addSuccess r1 r2 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;id&lt;span style="color:#f92672"&gt;=&lt;/span&gt;r1&lt;span style="color:#f92672"&gt;.&lt;/span&gt;id &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;; &amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; r2&lt;span style="color:#f92672"&gt;.&lt;/span&gt;id&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; addFailure s1 s2 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; s1 &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;; &amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; s2
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; plusAsync addSuccess addFailure v1 v2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;The key here is the introduction of the &lt;code&gt;Result&amp;lt;'TSuccess, 'TFailure&amp;gt;&lt;/code&gt; type and the terribly named &lt;code&gt;ID&lt;/code&gt; record type, which will capture relevant information for successful posts; namely, the post ID.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;&amp;gt;&amp;gt;@&lt;/code&gt; function is an asynchronous (hence the &lt;code&gt;@&lt;/code&gt;) version of the &lt;code&gt;&amp;gt;&amp;gt;=&lt;/code&gt; &lt;a href="https://fsharpforfunandprofit.com/posts/recipe-part2/#bind-as-a-piping-operation"&gt;bind-with-piping&lt;/a&gt; operator, which calls the &lt;code&gt;bindAsync&lt;/code&gt; function which, as you can guess, is the &lt;code&gt;async&lt;/code&gt; equivalent of the &lt;code&gt;bind&lt;/code&gt; operator from the original blog post. The &lt;code&gt;plusAsync&lt;/code&gt; function is the asynchronous version of the &lt;code&gt;plus&lt;/code&gt; function from the original blog post. All in all, if you can grok the Railway Oriented Programming post, these modifications are not that much more complex. They&amp;rsquo;re simply adapted for the &lt;code&gt;Async&amp;lt;Result&amp;lt;'s,'terr&amp;gt;&amp;gt;&lt;/code&gt; return type.&lt;/p&gt;
&lt;p&gt;When we get to the end of the code, we have:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fsharp" data-lang="fsharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; result &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; Success s &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; OkObjectResult&lt;span style="color:#f92672"&gt;(&lt;/span&gt;s&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&amp;gt;&lt;/span&gt; IActionResult
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; Failure f &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; StatusCodeResult&lt;span style="color:#f92672"&gt;(&lt;/span&gt;500&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&amp;gt;&lt;/span&gt; IActionResult
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;This is a really nice way to either return a &lt;code&gt;200 OK&lt;/code&gt; status code, along with a message that includes our successful IDs, or a &lt;code&gt;500 Server Error&lt;/code&gt; code, which, unfortunately, I haven&amp;rsquo;t figured out how to better manage.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve made it this far, then the remaining code in this function should be very easy to understand.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fsharp" data-lang="fsharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; tweet&lt;span style="color:#f92672"&gt;:&lt;/span&gt; Async&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;ID&lt;span style="color:#f92672"&gt;,&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; getTwitterClient
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;@&lt;/span&gt; writeTweet data
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; sheet&lt;span style="color:#f92672"&gt;:&lt;/span&gt; Async&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;ID&lt;span style="color:#f92672"&gt;,&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; getSheetService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;@&lt;/span&gt; writeToGoogleSheet data
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; toot&lt;span style="color:#f92672"&gt;:&lt;/span&gt; Async&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;ID&lt;span style="color:#f92672"&gt;,&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; getMastodonClient
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;@&lt;/span&gt; writeToot data
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;All that this does is define functions that use the &lt;code&gt;&amp;gt;&amp;gt;@&lt;/code&gt; async bind-with-pipe operator to post the link to the various services. I really like this code. The &lt;code&gt;run&lt;/code&gt; method is very clean, and aside from the subtleties of the syntax, the structure of the code makes it very clear what is happening. A little bit of work makes the code much, much cleaner. All we need to do now is write the code for each of these services. Ready for that? Let&amp;rsquo;s move onto the next part.&lt;/p&gt;
&lt;h3 id="step-4-integrate-external-services"&gt;Step 4: Integrate external services&lt;/h3&gt;
&lt;p&gt;For my link sharing function, I want to push content to (at least) three different sinks: a Google Sheets, which serves as kind of a bootleg database; Twitter; and Mastodon. To do this, I&amp;rsquo;ll use the &lt;a href="https://developers.google.com/api-client-library/dotnet/apis/sheets/v4"&gt;Google Sheets API&lt;/a&gt;, &lt;a href="https://github.com/linvi/tweetinvi"&gt;Tweetinvi&lt;/a&gt;, and &lt;a href="https://github.com/glacasa/Mastonet"&gt;Mastonet&lt;/a&gt;. These are all fairly easy to install with the dotnet package manager.&lt;/p&gt;
&lt;p&gt;One of the beautiful things about F# is that you can use any .Net library with it, even if it wasn&amp;rsquo;t built for F#. That means any C# library works with F# equally well. This is a long post and I&amp;rsquo;m not going to belabor how to do all of these things, but rather, I&amp;rsquo;ll cover the key steps and present the code.&lt;/p&gt;
&lt;h4 id="google-sheets"&gt;Google Sheets&lt;/h4&gt;
&lt;p&gt;This is by far the most complex integration because to make this work you need to use Google Cloud. Start by creating a new Google Sheet (you can just go to &lt;a href="https://sheets.new"&gt;sheets.new&lt;/a&gt;), or if you don&amp;rsquo;t have a Gmail account, create one first and then create a new Google Sheet. You can name it whatever, I called mine &amp;ldquo;Interesting Links&amp;rdquo;. In the first row, name the columns as follows: &lt;code&gt;url&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;added&lt;/code&gt;, &lt;code&gt;comment&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, navigate to &lt;a href="https://console.cloud.google.com"&gt;the Google Cloud console&lt;/a&gt;. Create a new project, and call it something obvious, like &lt;code&gt;Interesting Links&lt;/code&gt;. Select the project and navigate to the project dashboard. In the hamburger menu in the top left, go to &amp;ldquo;APIs &amp;amp; Services&amp;rdquo; and click &amp;ldquo;Enabled APIs &amp;amp; Services&amp;rdquo;. There, at the top of the screen, click &amp;ldquo;+ Enable APIs and Services&amp;rdquo;. In the search bar, click &amp;ldquo;Google Sheets&amp;rdquo; and enable the API.&lt;/p&gt;
&lt;p&gt;Once done, click &amp;ldquo;Manage&amp;rdquo;. On the left, you should see &amp;ldquo;Credentials&amp;rdquo;. Click this and you should see &amp;ldquo;API Keys&amp;rdquo;, &amp;ldquo;OAuth 2.0 Client IDs&amp;rdquo;, and &amp;ldquo;Service Accounts&amp;rdquo;. We&amp;rsquo;ll use a Service Account, although this may not be the best option. Perhaps in a future update I will fix this. Click &amp;ldquo;Create Credentials&amp;rdquo; at the top and choose &amp;ldquo;Service Account&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Give the Service Account a meaningful name, and click &amp;ldquo;Create and Continue.&amp;rdquo; On the next screen, it will ask you to choose a role. In the search box, type &amp;ldquo;Service Account Token Creator&amp;rdquo;. Skip the next step and click &amp;ldquo;Done&amp;rdquo;. This will return you to the Credentials screen. Click the Service Account you created, and at the top of the screen, click &amp;ldquo;Keys.&amp;rdquo; Next, click &amp;ldquo;Add Key&amp;rdquo; and &amp;ldquo;Create New Key&amp;rdquo;. This should pop a dialog box. Select &amp;ldquo;JSON&amp;rdquo; and click &amp;ldquo;Create&amp;rdquo;. This will create a JSON file for you and download it. Copy it somewhere meaningful, such as &lt;code&gt;~/.config/gcloud&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll base64 this string, and the easiest way with the command line is simply &lt;code&gt;base64 -w 0 ~/.config/gcloud/[KEY FILE].json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For local testing, we can either create an environment variable with this file, or we can put it in &lt;code&gt;local.settings.json&lt;/code&gt; with a field named &lt;code&gt;GOOGLE_SERVICE_ACCOUNT_CREDENTIAL&lt;/code&gt;. While we&amp;rsquo;re doing this, go to the spreadsheet you created and copy the sheet ID. It&amp;rsquo;s the long alphanumeric string that should be between &lt;code&gt;d/&lt;/code&gt; and &lt;code&gt;/edit&lt;/code&gt;. Put this in a variable called &lt;code&gt;GOOGLE_SPREADSHEET_ID&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Lastly, copy the Service Account email you created, which can be found on the Credentials page we visited earlier. In your Google Sheet, in the top right click &amp;ldquo;Share&amp;rdquo; and paste this email, giving it Editor permissions.&lt;/p&gt;
&lt;p&gt;All of this could have probably been done with terraform, but I&amp;rsquo;m going to be real. This is a one-time setup, and it&amp;rsquo;s complicated. I might have gotten it wrong here, but you can also Google how to set up a Google Sheets API credential. I don&amp;rsquo;t think it&amp;rsquo;s worth figuring out the terraform. Alternatively, we could have used Workload Identity Federation, but this, too, is overkill.&lt;/p&gt;
&lt;p&gt;Once we have the account set up, we can write the code to write to a spreadsheet. It&amp;rsquo;s fairly straightforward. First, we have to build a &lt;code&gt;SheetsService&lt;/code&gt; using the Google Cloud SDK. This requires our credential. Here, I define &lt;code&gt;getSheetService&lt;/code&gt; which does all of the work to turn the environment variable into a credential and uses this to initialize a &lt;code&gt;SheetsService&lt;/code&gt;. Here, we&amp;rsquo;re returning a &lt;code&gt;Success&lt;/code&gt; type containing the object we just initialized, so we need to create a record type, &lt;code&gt;Sheet&lt;/code&gt;, for that as well.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fsharp" data-lang="fsharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;open&lt;/span&gt; Google.Apis.Auth.OAuth2
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;open&lt;/span&gt; Google.Apis.Sheets.v4
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;open&lt;/span&gt; Google.Apis.Sheets.v4.Data
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;open&lt;/span&gt; Google.Apis.Services
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Sheet&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt; service &lt;span style="color:#f92672"&gt;:&lt;/span&gt; SheetsService &lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; getSheetService &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; async &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; scopes&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;list&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt; SheetsService.Scope.Spreadsheets &lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; getServiceAccountCredential &lt;span style="color:#f92672"&gt;(&lt;/span&gt;googleCredential&lt;span style="color:#f92672"&gt;:&lt;/span&gt; GoogleCredential&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; googleCredential&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CreateScoped&lt;span style="color:#f92672"&gt;(&lt;/span&gt;scopes&lt;span style="color:#f92672"&gt;).&lt;/span&gt;UnderlyingCredential
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;:?&amp;gt;&lt;/span&gt; ServiceAccountCredential
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; credential &lt;span style="color:#f92672"&gt;:&lt;/span&gt; ServiceAccountCredential &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; System.Environment.GetEnvironmentVariable&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;GOOGLE_SERVICE_ACCOUNT_CREDENTIAL&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&amp;gt;&lt;/span&gt; Convert.FromBase64String
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&amp;gt;&lt;/span&gt; Text.Encoding.UTF8.GetString
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&amp;gt;&lt;/span&gt; GoogleCredential.FromJson
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&amp;gt;&lt;/span&gt; getServiceAccountCredential
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; initializer&lt;span style="color:#f92672"&gt;:&lt;/span&gt; BaseClientService.Initializer &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; BaseClientService.Initializer&lt;span style="color:#f92672"&gt;(&lt;/span&gt;HttpClientInitializer&lt;span style="color:#f92672"&gt;=&lt;/span&gt;credential&lt;span style="color:#f92672"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ApplicationName&lt;span style="color:#f92672"&gt;=&lt;/span&gt;ApplicationName&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Success &lt;span style="color:#f92672"&gt;{&lt;/span&gt;service&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SheetsService&lt;span style="color:#f92672"&gt;(&lt;/span&gt;initializer&lt;span style="color:#f92672"&gt;)}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; ex &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Failure ex&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Message
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; writeToGoogleSheet &lt;span style="color:#f92672"&gt;(&lt;/span&gt;data&lt;span style="color:#f92672"&gt;:&lt;/span&gt; LinkData&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;input &lt;span style="color:#f92672"&gt;:&lt;/span&gt; Sheet&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; async &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; service&lt;span style="color:#f92672"&gt;:&lt;/span&gt; SheetsService &lt;span style="color:#f92672"&gt;=&lt;/span&gt; input&lt;span style="color:#f92672"&gt;.&lt;/span&gt;service
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; sheetId&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; System.Environment.GetEnvironmentVariable&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;GOOGLE_SHEET_ID&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; range&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;A:D&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; newItem&lt;span style="color:#f92672"&gt;:&lt;/span&gt; List&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;IList&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Object&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; List&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;IList&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Object&lt;span style="color:#f92672"&gt;&amp;gt;&amp;gt;&lt;/span&gt;()&lt;span style="color:#f92672"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; obj&lt;span style="color:#f92672"&gt;:&lt;/span&gt; List&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Object&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; List&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Object&lt;span style="color:#f92672"&gt;&amp;gt;([|&lt;/span&gt;data&lt;span style="color:#f92672"&gt;.&lt;/span&gt;url &lt;span style="color:#f92672"&gt;:&amp;gt;&lt;/span&gt; Object&lt;span style="color:#f92672"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data&lt;span style="color:#f92672"&gt;.&lt;/span&gt;title&lt;span style="color:#f92672"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; DateTime.UtcNow&lt;span style="color:#f92672"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data&lt;span style="color:#f92672"&gt;.&lt;/span&gt;comment&lt;span style="color:#f92672"&gt;|]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; newItem&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Add&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;obj&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; request &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; service&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Spreadsheets&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Values&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Append&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ValueRange&lt;span style="color:#f92672"&gt;(&lt;/span&gt;Values&lt;span style="color:#f92672"&gt;=&lt;/span&gt;newItem&lt;span style="color:#f92672"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sheetId&lt;span style="color:#f92672"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; range&lt;span style="color:#f92672"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; InsertDataOption&lt;span style="color:#f92672"&gt;=&lt;/span&gt;SpreadsheetsResource.ValuesResource.AppendRequest.InsertDataOptionEnum.INSERTROWS&lt;span style="color:#f92672"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValueInputOption&lt;span style="color:#f92672"&gt;=&lt;/span&gt;SpreadsheetsResource.ValuesResource.AppendRequest.ValueInputOptionEnum.USERENTERED&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let!&lt;/span&gt; response &lt;span style="color:#f92672"&gt;=&lt;/span&gt; request&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ExecuteAsync() &lt;span style="color:#f92672"&gt;|&amp;gt;&lt;/span&gt; Async.AwaitTask
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Success &lt;span style="color:#f92672"&gt;{&lt;/span&gt;id&lt;span style="color:#f92672"&gt;=&lt;/span&gt;response&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Updates&lt;span style="color:#f92672"&gt;.&lt;/span&gt;UpdatedRange&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ex&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;exn&lt;/span&gt;&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Failure ex&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Message
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;In &lt;code&gt;writeGoogleSheets&lt;/code&gt;, we consider each row to be a list of objects, and each sheet range to be a list of rows, and hence a list of lists of objects. I know I have four columns, so I can simply define my sheet range using open-ended columns, or &lt;code&gt;A:D&lt;/code&gt;. We create a new row with the relevant fields, &lt;code&gt;url&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;added&lt;/code&gt;, and &lt;code&gt;comment&lt;/code&gt;, although they appear here unnamed, and simply append this using the API. We construct the request, and by using &lt;code&gt;try-with&lt;/code&gt;, we can either return a &lt;code&gt;Success&lt;/code&gt; if the operation succeeds, or a &lt;code&gt;Failure&lt;/code&gt; containing an exception message if the operation fails.&lt;/p&gt;
&lt;p&gt;This code is again very simple. We&amp;rsquo;re simply fetching a credential, initializing a sheet service, crafting the data to be added, and adding it.&lt;/p&gt;
&lt;h4 id="twitter"&gt;Twitter&lt;/h4&gt;
&lt;p&gt;Google Sheets was very complicated, but Twitter is a bit easier. To post to Twitter, the important thing to know is you&amp;rsquo;ll need an &lt;strong&gt;Individual Developer Account&lt;/strong&gt; with &lt;strong&gt;elevated privileges&lt;/strong&gt;. This can be done at the &lt;a href="https://developer.twitter.com/en/portal/dashboard"&gt;Twitter Developer Portal&lt;/a&gt;, but I&amp;rsquo;ll be honest the portal is badly designed and things are really hard to find. It took me an hour, and now that I&amp;rsquo;ve done it once, I can&amp;rsquo;t go back to figure out how to tell you how to do it. All I can tell you are the important words: &lt;strong&gt;individual developer account&lt;/strong&gt; and &lt;strong&gt;elevated privileges&lt;/strong&gt;. Click around. Godspeed.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll need to create a new project, I called mine &amp;ldquo;Emily&amp;rsquo;s Syndicator&amp;rdquo;. This allows me to post statuses to Twitter. Once you do this, on the left you should see your project. Click it, and toward the top of the screen you should see &amp;ldquo;Keys and tokens.&amp;rdquo; Here, you can generate the required keys. I&amp;rsquo;m using an &amp;ldquo;Access token and secret&amp;rdquo;, which I copy to a JSON file locally and also can put in &lt;code&gt;local.settings.json&lt;/code&gt; in fields called &lt;code&gt;TWITTER_ACCESS_TOKEN&lt;/code&gt; and &lt;code&gt;TWITTER_ACCESS_TOKEN_SECRET&lt;/code&gt; for local testing.&lt;/p&gt;
&lt;p&gt;Next, generate an API key and secret, sometimes called a consumer key and secret, and store these locally in fields named &lt;code&gt;TWITTER_CONSUMER_KEY&lt;/code&gt; and &lt;code&gt;TWITTER_CONSUMER_KEY_SECRET&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There are better ways to authenticate with Twitter, but everything is badly documented. This works, and I&amp;rsquo;ll improve it later. To post, I&amp;rsquo;m using the Tweetinvi SDK. Tweetinvi hasn&amp;rsquo;t been updated in a couple of years, but it works. Just like with Google Sheets, we&amp;rsquo;ll need to create a record type to receive the &lt;code&gt;Success&lt;/code&gt; case, otherwise the workflow is even simpler.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fsharp" data-lang="fsharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;open&lt;/span&gt; Tweetinvi
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Twitter&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt; twitterClient &lt;span style="color:#f92672"&gt;:&lt;/span&gt; TwitterClient &lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; buildPostFromData &lt;span style="color:#f92672"&gt;(&lt;/span&gt;data&lt;span style="color:#f92672"&gt;:&lt;/span&gt; LinkData&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; data&lt;span style="color:#f92672"&gt;.&lt;/span&gt;comment &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; data&lt;span style="color:#f92672"&gt;.&lt;/span&gt;url
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; getTwitterClient &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; async &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Success &lt;span style="color:#f92672"&gt;{&lt;/span&gt;twitterClient&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; TwitterClient&lt;span style="color:#f92672"&gt;(&lt;/span&gt;System.Environment.GetEnvironmentVariable&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;TWITTER_CONSUMER_KEY&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; System.Environment.GetEnvironmentVariable&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;TWITTER_CONSUMER_KEY_SECRET&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; System.Environment.GetEnvironmentVariable&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;TWITTER_ACCESS_TOKEN&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; System.Environment.GetEnvironmentVariable&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;TWITTER_ACCESS_TOKEN_SECRET&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;))}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; ex &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Failure ex&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Message
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; writeTweet &lt;span style="color:#f92672"&gt;(&lt;/span&gt;data&lt;span style="color:#f92672"&gt;:&lt;/span&gt; LinkData&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;input &lt;span style="color:#f92672"&gt;:&lt;/span&gt; Twitter&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; async &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; client &lt;span style="color:#f92672"&gt;=&lt;/span&gt; input&lt;span style="color:#f92672"&gt;.&lt;/span&gt;twitterClient
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; tweet &lt;span style="color:#f92672"&gt;=&lt;/span&gt; buildPostFromData data
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; post &lt;span style="color:#f92672"&gt;=&lt;/span&gt; client&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Tweets&lt;span style="color:#f92672"&gt;.&lt;/span&gt;PublishTweetAsync&lt;span style="color:#f92672"&gt;(&lt;/span&gt;tweet&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Success &lt;span style="color:#f92672"&gt;{&lt;/span&gt;id&lt;span style="color:#f92672"&gt;=&lt;/span&gt;post&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Result&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Id&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ToString()&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; ex &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Failure ex&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Message
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;This code is extremely straightforward. First, we construct the client using the Twitter credentials obtained as described above. Then, we construct a post from the &lt;code&gt;comment&lt;/code&gt; and &lt;code&gt;url&lt;/code&gt; fields in the JSON payload. Last, we publish the Tweet and return the Tweet ID. It really could not be simpler.&lt;/p&gt;
&lt;h4 id="mastodon"&gt;Mastodon&lt;/h4&gt;
&lt;p&gt;Mastodon is even easier than Twitter. The setup is quite simple. From your Mastodon account on web, click on &amp;ldquo;Preferences&amp;rdquo; and then choose &amp;ldquo;Development&amp;rdquo;. Create a new application, give it a meaningful name, and unselect all scopes except &lt;code&gt;write:statuses&lt;/code&gt;. Click &amp;ldquo;Submit&amp;rdquo; and you&amp;rsquo;ll retrieve an Authentication token. Store this in a local environment variable (or in &lt;code&gt;local.settings.json&lt;/code&gt;, as before) called &lt;code&gt;MASTODON_ACCESS_TOKEN&lt;/code&gt; and while we&amp;rsquo;re here, put your server name in as well, as &lt;code&gt;MASTODON_SERVER&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once complete, we&amp;rsquo;ll follow nearly the identical steps we did for Twitter, just using the Mastonet SDK instead.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fsharp" data-lang="fsharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;open&lt;/span&gt; Mastonet
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Mastodon&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt; mastodonClient&lt;span style="color:#f92672"&gt;:&lt;/span&gt; MastodonClient &lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; getMastodonClient &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; async &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; client &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; MastodonClient&lt;span style="color:#f92672"&gt;(&lt;/span&gt;System.Environment.GetEnvironmentVariable&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;MASTODON_SERVER&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; System.Environment.GetEnvironmentVariable&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;MASTODON_ACCESS_TOKEN&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Success &lt;span style="color:#f92672"&gt;{&lt;/span&gt; mastodonClient&lt;span style="color:#f92672"&gt;=&lt;/span&gt;client &lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; ex &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Failure ex&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Message
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; writeToot &lt;span style="color:#f92672"&gt;(&lt;/span&gt;data&lt;span style="color:#f92672"&gt;:&lt;/span&gt; LinkData&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;input&lt;span style="color:#f92672"&gt;:&lt;/span&gt; Mastodon&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; async &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; client &lt;span style="color:#f92672"&gt;=&lt;/span&gt; input&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mastodonClient
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; post &lt;span style="color:#f92672"&gt;=&lt;/span&gt; buildPostFromData data
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; result &lt;span style="color:#f92672"&gt;=&lt;/span&gt; client&lt;span style="color:#f92672"&gt;.&lt;/span&gt;PublishStatus&lt;span style="color:#f92672"&gt;(&lt;/span&gt;post&lt;span style="color:#f92672"&gt;).&lt;/span&gt;Result
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Success &lt;span style="color:#f92672"&gt;{&lt;/span&gt; id&lt;span style="color:#f92672"&gt;=&lt;/span&gt;result&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Id &lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; ex &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Failure ex&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Message
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;Piece of cake.&lt;/p&gt;
&lt;p&gt;With each of these, notice how I split the process up into two steps: first we construct the client or service, and then we execute the posting. These are all asynchronous operations, and we chain them together using the &lt;code&gt;&amp;gt;&amp;gt;@&lt;/code&gt; async bind-with-piping operator. We bind each of those, which lets us then parallelize them as described earlier. Altogether, the code should be very easy and very minimal. The hard part is configuring the various accounts.&lt;/p&gt;
&lt;p&gt;Once we put this all together, it should come out to ~200 lines of code. I&amp;rsquo;ve copied my file (which might have some small differences) to &lt;a href="https://gist.github.com/Gorcenski/ac5e3f08c4302f30d4a5d877ec182a70"&gt;a public Gist&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="testing-locally"&gt;Testing locally&lt;/h3&gt;
&lt;p&gt;If everything is working right, then you should be able to execute this locally fairly easily. As before, run &lt;code&gt;func host start&lt;/code&gt;, and it should fire up a webserver at &lt;code&gt;localhost:7071&lt;/code&gt;. Use a cURL request to post a JSON payload as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -X POST http://localhost:7071/api/LinkSharer -d &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;url&amp;#34;: &amp;#34;test&amp;#34;, &amp;#34;title&amp;#34;: &amp;#34;test title&amp;#34;, &amp;#34;comment&amp;#34;: &amp;#34;test comment&amp;#34;}&amp;#39;&lt;/span&gt; -v
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If everything works, go check your social media services! You should see test messages. Don&amp;rsquo;t forget to delete these messages, of course ;)&lt;/p&gt;
&lt;h3 id="wrapping-up"&gt;Wrapping up&lt;/h3&gt;
&lt;p&gt;This was a very long post, but hopefully you followed it. I tried to structure it step-by-step. Of course, you can skip any of the social media services. If you don&amp;rsquo;t want to use Google Sheets as a data store, then you can just ignore all that! Altogether this isn&amp;rsquo;t a lot of code, and I like how the code can be very structured and clean. There&amp;rsquo;s very little boilerplate, and you can really focus on the functional flow of the code, rather than state management and object handling. The railway pattern works &lt;em&gt;really well&lt;/em&gt; with asynchronous programming. I like this result, even if the setup was a pain in the ass.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re still not out of the woods yet. We still need to set up Azure to deploy this function to a live environment. I&amp;rsquo;ll write that post tomorrow.&lt;/p&gt;
&lt;p&gt;For now, I hope you liked this F#-based approach, and thanks for sticking it out with this long post!&lt;/p&gt;</description></item><item><title>Posts without posting: building a social media sharing relay with F♯ and Azure Functions, part 1</title><link>https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-1/</link><pubDate>Tue, 27 Dec 2022 04:20:56 +0100</pubDate><guid>https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-1/</guid><image><url>https://emilygorcenski.com/photo-gallery/writing/link-sharing.png"</url></image><enclosure url="https://emilygorcenski.com/photo-gallery/writing/link-sharing.png" length="" type="image/jpeg"/><media:content url="https://emilygorcenski.com/photo-gallery/writing/link-sharing.png" width="850" medium="image" type="image/jpeg"><media:thumbnail url="https://emilygorcenski.com/photo-gallery/writing/link-sharing.png" width="600"/></media:content><description>&lt;p&gt;Social media is getting more complicated, and building tools to automate sharing content can help simplify the fragmentation of social networks. Here&amp;rsquo;s how I post without having to log into social media.&lt;/p&gt;
&lt;h1 id="part-1-architecting-a-relay"&gt;Part 1: Architecting a relay&lt;/h1&gt;
&lt;p&gt;Sharing content across multiple social media feeds can be a real pain. Social media links are fragile. Networks can ban you, or force you to remove your posts. They often use link shorteners, modifying the original link, which adds another possible point of failure. Social media is built for the moment, and it&amp;rsquo;s not built to make bridging networks easy. If you want to share in multiple places, or to keep a record of what you&amp;rsquo;re sharing, including the original, unmodified URL, that can be a challenge.&lt;/p&gt;
&lt;blockquote&gt;
&lt;h4 id="posts-without-posting-a-series"&gt;Posts without posting, a series&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-1/"&gt;Architecting a relay&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-2/"&gt;Writing the code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-3/"&gt;Configuring Azure Functions with Terraform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://emilygorcenski.com/post/posts-without-posting-building-a-social-media-sharing-relay-with-f-and-azure-functions-part-4/"&gt;Setting up an iOS Shortcut&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;After Elon Musk&amp;rsquo;s takeover of Twitter, I &lt;a href="https://emilygorcenski.com/post/angelheaded-hipsters-burning-for-the-ancient-heavenly-connection/"&gt;decided to stop using the site&lt;/a&gt; day-to-day. Instead, I want to use it, and my relatively large following there, to act as a syndication engine, following the Indieweb principles of &lt;a href="https://emilygorcenski.com/post/posse-comitatus-twitter-as-a-syndication-engine/"&gt;POSSE&lt;/a&gt;: &lt;strong&gt;P&lt;/strong&gt;ublish &lt;strong&gt;Own&lt;/strong&gt; &lt;strong&gt;S&lt;/strong&gt;ite, &lt;strong&gt;S&lt;/strong&gt;yndicate &lt;strong&gt;E&lt;/strong&gt;lsewhere. I still want to share what I find interesting, and I want to share what I write and create. But as I migrate to the &lt;a href="https://indieweb.social/@emilygorcenski"&gt;indieweb.social&lt;/a&gt; server on Mastodon, I want my sharing to be automated and mindless. My ideal workflow is to read an interesting article, press a button, type a message, and automatically share the link to the article with my comment everywhere I have an online presence, all without requiring any third-party apps.&lt;/p&gt;
&lt;p&gt;For me currently, that means sharing in three places: &lt;a href="https://twitter.com/EmilyGorcenski"&gt;Twitter&lt;/a&gt;, &lt;a href="https://indieweb.social/@emilygorcenski"&gt;Mastodon&lt;/a&gt;, and the &lt;a href="https://emilygorcenski.com"&gt;front page&lt;/a&gt; of this site. Before building this solution, I was using Zapier to serve as a relay for this content. However, Zapier&amp;rsquo;s free tier does not let you use webhooks, and the service does not have a Mastodon plug-in. This mean that to use REST functionality, including cross-posting &lt;em&gt;to&lt;/em&gt; Mastodon, I had to subscribe to their cheapest tier, which at around $20 a month is not cheap. I wanted a cheaper solution, one I could manage entirely myself. Technically, I could have done all of this entirely with an iPhone Shortcut, &lt;em&gt;if&lt;/em&gt; I had the Twitter app installed. Since I haven&amp;rsquo;t had the app on my phone in almost a year, this wouldn&amp;rsquo;t do. As I&amp;rsquo;ll explain in this post, I&amp;rsquo;ll still use an iPhone Shortcut, but I&amp;rsquo;m pushing most of the business logic to a serverless Azure Function App. This is a solution that is practically free, if you know how to do it. Lucky you, over the next few posts, I&amp;rsquo;m going to show you how.&lt;/p&gt;
&lt;p&gt;In this post, I&amp;rsquo;m going to describe the architecture of how I set this up. I do not know how to build something similar with Android; I do not have access to an Android phone at the moment. I&amp;rsquo;m almost positive, however, that it can be done with similar ease.&lt;/p&gt;
&lt;p&gt;To get started, here&amp;rsquo;s the tech I&amp;rsquo;m going to use.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Microsoft Azure&lt;/strong&gt; — For low-cost hosting, and because I use it to host the rest of my site&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Terraform&lt;/strong&gt; — For setting up the Azure Functions App using Infrastructure-as-Code&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;F#&lt;/strong&gt; — Because I love the language and I want to practice it more&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VSCode&lt;/strong&gt; — A fantastic development environment&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;iOS Shortcuts&lt;/strong&gt; — A built-in app on my iPhone to let me build the workflow for free&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Sheets&lt;/strong&gt; — A simple, free, high-performance data store for efficient reads, infrequent writes, with an unbeatable SLA for the cost&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Github Actions&lt;/strong&gt; — A simple, low-cost CI/CD environment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To get started, you&amp;rsquo;ll need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A Github account (free)&lt;/li&gt;
&lt;li&gt;An Azure account (costs vary and you need a credit card to set this up, but I pay ~$9 a month for almost all my hosting needs. The feature described in this post costs me $0.01 monthly)&lt;/li&gt;
&lt;li&gt;A Gmail account (free)&lt;/li&gt;
&lt;li&gt;A Google cloud account attached to that Gmail account (free, you might need a credit card here as well, but what I am discussing costs exactly $0 monthly)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The link sharing workflow begins when I read an interesting article on my phone. Using the default iOS share tray, I add a sharing option that collects the page URL, the page title, and opens a dialog for me to write a comment about the post. I&amp;rsquo;ll call this shortcut &lt;code&gt;Post Link&lt;/code&gt;. After writing a comment, I click Submit, and the shortcut packages everything into a small JSON payload and calls a serverless Azure Function App REST API authenticated with an authentication token, which gets passed along in a header.&lt;/p&gt;
&lt;p&gt;The Function App, which I call &lt;code&gt;LinkSharer&lt;/code&gt;, takes the JSON payload, crafts a simple post, and performs a sequence of asynchronous API calls to put the link and the comment to various social media outlets. For my website, I also write another Function App called &lt;code&gt;LinkFetcher&lt;/code&gt; which collects the last &lt;code&gt;n&lt;/code&gt; links and displays them on my website.&lt;/p&gt;
&lt;p&gt;Altogether, the workflow looks something like this.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://emilygorcenski.com/photo-gallery/writing/link-sharing.png" alt="Component diagram of the above-described workflow."&gt;&lt;/p&gt;
&lt;p&gt;The first question you might ask is, &amp;ldquo;Google Sheets? &lt;em&gt;Really?&lt;/em&gt;&amp;rdquo; My answer is yes, really. For mostly-read, seldom-write, rarely-update workflows, Google Sheets is probably the lowest friction, lowest cost option you have for a durable backing store out there. Simply put, you&amp;rsquo;re not going to beat Google&amp;rsquo;s SLA with a free offering. You don&amp;rsquo;t have to worry about backups, it&amp;rsquo;s dirt-simple to set up, hand-editable, and it is highly-performant. Heck, it even comes with an out-of-the-box JSON API if you don&amp;rsquo;t want to write your own reader facade. Google Sheets has limitations, big ones. But for quick-and-dirty JAM stack development, there are worse options out there. Eventually, I will replace this with a proper database. But I&amp;rsquo;m not ready to spend the money yet and I want to focus on the rest of the feature work. So Google Sheets it is.&lt;/p&gt;
&lt;p&gt;In terms of my choice of Azure, &lt;a href="https://emilygorcenski.com/post/migrating-a-static-site-to-azure-with-terraform/"&gt;I&amp;rsquo;ve been on Azure&lt;/a&gt; for a couple of years now. It has limitations, but for personal use, it&amp;rsquo;s great. I spend $5-10 monthly for &lt;em&gt;everything&lt;/em&gt;. That will increase when I set up a proper database, but most of the cost right now is the CDN. Essentially, for $5 monthly I can host my site and have incredible DDoS protection. It&amp;rsquo;s not bad. But my favorite feature of Azure is that I can deploy Azure Functions written in .Net, which means I can write F#. If you haven&amp;rsquo;t tried F#, it&amp;rsquo;s an &lt;em&gt;amazing&lt;/em&gt; language. I&amp;rsquo;m not a very good functional programmer; if you ask me what a monad is, I&amp;rsquo;m going to lecture you how real mathematicians think Category Theory is a bunch of abstract useless gibberish, and I&amp;rsquo;m also not going to answer your question because I don&amp;rsquo;t actually know. I like F# because the community is averse to this kind of jargon, but also because the language is beautiful and fun to write. Honestly, I wish I could write it every day.&lt;/p&gt;
&lt;p&gt;Enough asides. To get started on the above workflow, I&amp;rsquo;ll develop in three phases.&lt;/p&gt;
&lt;p&gt;First, I&amp;rsquo;ll actually code up the Azure Function and run it locally. This will let me test everything before deployment. I&amp;rsquo;ll need the &lt;a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=v4%2Cwindows%2Ccsharp%2Cportal%2Cbash#install-the-azure-functions-core-tools"&gt;Azure Functions Core Tools&lt;/a&gt; and the &lt;a href="https://dotnet.microsoft.com/en-us/download/dotnet/6.0"&gt;.Net runtime&lt;/a&gt; installed for this. (As of this writing, I am using .Net 6.0, as .Net 7.0 still has some kinks when it comes to Azure Functions.)&lt;/p&gt;
&lt;p&gt;In the second step, I&amp;rsquo;ll set up my infrastructure so that I can actually deploy the code to Azure Functions. This will involve setting up the function, and also setting up some Azure Key Vault secrets. There are a couple bits I haven&amp;rsquo;t figured out how to do, so it&amp;rsquo;s not pure infra-as-code here. Namely, it seems like Azure Function App Custom Domains aren&amp;rsquo;t working with Terraform as of this writing. In this step I&amp;rsquo;ll also set up a CI/CD workflow with Github actions to automate the deployment of the Azure Function.&lt;/p&gt;
&lt;p&gt;Third, I&amp;rsquo;ll shift my focus to a low-code environment, and build a simple iOS shortcut to use the workflow.&lt;/p&gt;
&lt;p&gt;The writeups for these posts I&amp;rsquo;ll be publishing in the following days. In the meanwhile, this post should give you an overview of the workflow and the architecture. It&amp;rsquo;s not complex, but some of what I describe might feel overly technical. If you want a simplified version of these posts at some point, &lt;a href="https://indieweb.social/@emilygorcenski"&gt;let me know&lt;/a&gt;. I&amp;rsquo;m confident that the average person familiar with a computer can get this working for themselves with some help.&lt;/p&gt;
&lt;p&gt;In the meantime, I&amp;rsquo;m really enjoying being able to share content with &lt;em&gt;all&lt;/em&gt; of my followers without having to log into social media.&lt;/p&gt;
&lt;p&gt;Until tomorrow.&lt;/p&gt;</description></item></channel></rss>