<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mailmeteor</title>
    <description>The latest articles on DEV Community by Mailmeteor (@mailmeteor).</description>
    <link>https://dev.to/mailmeteor</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F5552%2F32e6c792-11fe-4cd6-9654-486a6c52840a.png</url>
      <title>DEV Community: Mailmeteor</title>
      <link>https://dev.to/mailmeteor</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mailmeteor"/>
    <language>en</language>
    <item>
      <title>How to schedule tasks in more than 30 days in Google Cloud Tasks API?</title>
      <dc:creator>Corentin</dc:creator>
      <pubDate>Mon, 28 Mar 2022 13:40:55 +0000</pubDate>
      <link>https://dev.to/mailmeteor/how-to-schedule-tasks-in-more-than-30-days-in-google-cloud-tasks-api-35f</link>
      <guid>https://dev.to/mailmeteor/how-to-schedule-tasks-in-more-than-30-days-in-google-cloud-tasks-api-35f</guid>
      <description>&lt;p&gt;At Mailmeteor, we rely heavily on &lt;a href="https://cloud.google.com/tasks"&gt;Google Cloud Tasks&lt;/a&gt; to send emails. In fact, each time we send an email, there's one (or more) Cloud Tasks associated to it. That's a lot of tasks in the end.&lt;/p&gt;

&lt;p&gt;While Google's product is really robust, one thing that has always being tricky is that you can't schedule a task that will run in more than 30 days.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Maximum schedule time for a task&lt;/td&gt;
&lt;td&gt;30 days from current date and time&lt;/td&gt;
&lt;td&gt;The maximum amount of time in the future that a task can be scheduled.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Extract from &lt;a href="https://cloud.google.com/tasks/docs/quotas"&gt;Google Cloud Tasks documentation on quotas &amp;amp; limits&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It is still way more than what AWS proposes (AWS SQS - Simple Queue Service - lets you queue messages for up to 15 minutes). Nevertheless, there are so many use cases when having a very-long tasks scheduler is needed.&lt;/p&gt;

&lt;p&gt;While I wasn't sure why Google has limited the execution delay to a month, one of their employee has explained on StackOverflow that such limit "&lt;em&gt;is a design decision. Google does not charge for the storage space of tasks, so extending that would detrimental to our costs.&lt;/em&gt;" (&lt;a href="https://stackoverflow.com/questions/58530361/how-increase-maximum-schedule-time-in-gcloud-tasks-api#comment103417942_58544657"&gt;source&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Though, Google Cloud Tasks is already a paid product. So extending the date, whether you need to pay for it or not, wouldn't be that much of an issue for them. In fact, according to &lt;a href="https://stackoverflow.com/questions/58530361/how-increase-maximum-schedule-time-in-gcloud-tasks-api#comment103417942_58544657"&gt;this StackOverflow thread&lt;/a&gt;, more than 1,000 people have been interested in extending the task delay. And there's already a &lt;a href="https://issuetracker.google.com/issues/175930182"&gt;feature request&lt;/a&gt;, back from 2020, which I urge you to star to make sure Google prioritizes this.&lt;/p&gt;

&lt;p&gt;Too much talking. Let's see how we can keep using Google Cloud Tasks and extend the execution delay "to infinity and beyond". &lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;The trick is in adding an &lt;code&gt;ETA&lt;/code&gt; header to your tasks. This way, before executing the task, you can check if the ETA is now (and thus execute the task) or in the future (and thus re-schedule the task). This way you can recursively keep creating tasks and eventually execute your task at your desired time.&lt;/p&gt;

&lt;p&gt;Let's take an example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I have a task to run in 45 days&lt;/li&gt;
&lt;li&gt;I create a new task with the max execution time (30 days)&lt;/li&gt;
&lt;li&gt;Then:

&lt;ul&gt;
&lt;li&gt;30 days later, the task executes, but it's too early, so I reschedule it in 45-30 = 14 days&lt;/li&gt;
&lt;li&gt;14 days later (45 days in total), the task executes normally.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In fact, doing so let's you create tasks in 1 year (or more) from now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation (JS)
&lt;/h2&gt;

&lt;p&gt;In Express.js, all you need is a middleware that will check if the execution time is in the future, and if so will reschedule the tasks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Middleware to reschedule Google Cloud Tasks&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;googleTasksScheduleMiddleware&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;taskETAHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;google-cloud-tasks-eta&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="c1"&gt;// If no header, skip middleware&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskETAHeader&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;taskETAHeader&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;intHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskETAHeader&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Time has passed, let's process the task now&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intHeader&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// It's too early! Reschedule the task&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Construct the task.&lt;/span&gt;
    &lt;span class="nx"&gt;createTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Re-scheduled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    
    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, add your middle before the first routes of your application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;googleTasksScheduleMiddleware&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As you can see, it's pretty easy to implement and doesn't require refactoring your application. If you are interested in more engineering articles from Mailmeteor, make sure to &lt;a href="https://dev.to/frenchcooc"&gt;follow my account&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>googlecloud</category>
      <category>programming</category>
      <category>devops</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Cannot find name 'Bugsnag'</title>
      <dc:creator>Corentin</dc:creator>
      <pubDate>Wed, 03 Nov 2021 13:28:07 +0000</pubDate>
      <link>https://dev.to/mailmeteor/cannot-find-name-bugsnag-1d8h</link>
      <guid>https://dev.to/mailmeteor/cannot-find-name-bugsnag-1d8h</guid>
      <description>&lt;p&gt;When you get started with Bugsnag, they have a handy guide to help you make sure errors are well reported to their monitoring tool. In &lt;a href="https://mailmeteor.com"&gt;Mailmeteor&lt;/a&gt;, we are using Bugsnag to make sure our tools are robust and bug-free.&lt;/p&gt;

&lt;p&gt;But recently, when initializing a new TypeScript project on Bugsnag, I end up having this error: &lt;code&gt;Cannot find name 'Bugsnag'&lt;/code&gt; (which might also be seen as &lt;code&gt;Bugsnag is not defined&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jUwszmly--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vyg9bgrli8wylabagfkc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jUwszmly--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vyg9bgrli8wylabagfkc.png" alt="Cannot find name 'Bugsnag'" width="540" height="183"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick fix
&lt;/h2&gt;

&lt;p&gt;Just add &lt;code&gt;import Bugsnag from '@bugsnag/js'&lt;/code&gt;. The type error is gone :-)&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>vue</category>
      <category>typescript</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to add a sparkline to your Vue.js app</title>
      <dc:creator>Corentin</dc:creator>
      <pubDate>Tue, 13 Jul 2021 14:02:31 +0000</pubDate>
      <link>https://dev.to/mailmeteor/how-to-add-a-sparkline-for-your-vue-js-app-1c4h</link>
      <guid>https://dev.to/mailmeteor/how-to-add-a-sparkline-for-your-vue-js-app-1c4h</guid>
      <description>&lt;p&gt;Very recently, I was looking to add a neat sparkline to a Vue.js application of my own. &lt;/p&gt;

&lt;p&gt;As always, I googled just that, looking for &lt;a href="https://www.google.com/search?q=sparkline+vue.js"&gt;sparkline vue.js&lt;/a&gt; or &lt;a href="https://www.google.com/search?q=sparkline+npm"&gt;sparkline npm&lt;/a&gt;. But I couldn't find something that was easy, with a small footprint and yet customizable.&lt;/p&gt;

&lt;p&gt;After playing a bit with &lt;a href="https://www.chartjs.org/"&gt;Chart.js&lt;/a&gt;, I just stopped and considered how I could build a decent, yet very simple, sparkline component (i.e. without any dependency).&lt;/p&gt;

&lt;p&gt;If you look at how npm's sparkline works as well as the ones from Stripe's dashboard, you will quickly realize that it's just a SVG element that you customize with JavaScript.&lt;/p&gt;

&lt;p&gt;So bear with me, I'll show you how to do just that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;iframe src="https://codesandbox.io/embed/sparkline-ex2dn"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Behind the scene
&lt;/h2&gt;

&lt;p&gt;The sparkline is just a Vue.js component where you provide the coordinates of the sparkline as an array. Here's how I've rendered the sparkline in the example above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;sparkline&lt;/span&gt; &lt;span class="na"&gt;v-bind:data=&lt;/span&gt;&lt;span class="s"&gt;"[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31]"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/sparkline&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The source code of the component is the following:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;As you might have noticed, the code renders an &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; HTML element by computing the coordinates of the different &lt;code&gt;&amp;lt;path&amp;gt;&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;There are two &lt;code&gt;&amp;lt;path&amp;gt;&lt;/code&gt;. One for the blue line. And another one for the blue background. I've used the color &lt;code&gt;#1f8ceb&lt;/code&gt; but of course this is totally customizable, just like the width/height of the sparkline.&lt;/p&gt;

&lt;p&gt;That component is pretty basic and contrary to NPM or Stripe, it doesn't handle when a mouse hovers the &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;. I didn't need that for my use case, but if ever you implement it, feel free to edit the &lt;a href="https://gist.github.com/Frenchcooc/e4748ad6275984a01868153e3c0d8a1e"&gt;gist&lt;/a&gt; and share it with the community.&lt;/p&gt;

</description>
      <category>vue</category>
      <category>javascript</category>
      <category>showdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Basic mail merge script for Gmail (using Google Apps Script)</title>
      <dc:creator>Corentin</dc:creator>
      <pubDate>Mon, 12 Apr 2021 15:04:32 +0000</pubDate>
      <link>https://dev.to/mailmeteor/basic-mail-merge-script-for-gmail-using-google-apps-script-4ok4</link>
      <guid>https://dev.to/mailmeteor/basic-mail-merge-script-for-gmail-using-google-apps-script-4ok4</guid>
      <description>&lt;p&gt;Sending mass emails from Gmail is sometimes seen as a challenge. I can tell you it's not! In this article we'll look at the basics of building a mail merge script for Gmail and how it can fit your email marketing needs.&lt;/p&gt;

&lt;p&gt;First, here's what you need to send emails in bulk with Gmail:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a Gmail (or Google Workspace) account&lt;/li&gt;
&lt;li&gt;a list of contacts&lt;/li&gt;
&lt;li&gt;a template of the emails you want to send&lt;/li&gt;
&lt;li&gt;basic development skills&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let's see the 6 steps to reproduce to create your Google script to send emails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tutorial: Building a mail merge script for Gmail
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;a href="https://script.google.com/" rel="noopener noreferrer"&gt;Google Apps Script&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Click on the "&lt;strong&gt;New project&lt;/strong&gt;" button to create a new project.&lt;/li&gt;
&lt;li&gt;You should now see the script editor.
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmailmeteor.com%2Fassets%2Fimg%2Fblog%2Fmail-merge-gmail%2Fmail-merge-gmail-script-gas-editor.png" alt="Google Apps Script editor"&gt;
&lt;/li&gt;
&lt;li&gt;From the editor, copy and paste the following script:
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

Update the &lt;code&gt;recipients&lt;/code&gt; variable with your list of recipients. Also update the &lt;code&gt;template&lt;/code&gt; subject and content to make it fit your needs&lt;/li&gt;
&lt;li&gt;Click on "&lt;strong&gt;Save&lt;/strong&gt;" to save your changes&lt;/li&gt;
&lt;li&gt;Then click on "&lt;strong&gt;▶️ Run&lt;/strong&gt;" to send your emails.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Note: the first time you use this script it will ask for the permission to send emails on your behalf. Then it won't ask you again.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Emails are usually immediately delivered, but sometimes it takes a few seconds. Check your &lt;a href="https://mail.google.com/mail/#sent" rel="noopener noreferrer"&gt;"Sent" folder in Gmail&lt;/a&gt; to confirm that all emails have been sent! As you will see, all your recipients receive a unique email sent from your email address. You don't need to use &lt;em&gt;bcc&lt;/em&gt; or any other techniques.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Some say that it's impossible to send emails in bulk with Gmail. It's totally untrue. Of course, this is a very basic macro for Google Sheets to send lots of emails with Gmail. You might need to adapt it a little bit more, but thanks to Google Apps Script you can do lot of things.&lt;/p&gt;

&lt;p&gt;Before you go, here are two things to keep in mind when using Gmail as an email marketing tool:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Be sure to abide by &lt;a href="https://support.google.com/mail/answer/81126?hl=en" rel="noopener noreferrer"&gt;Gmail bulk senders guidelines&lt;/a&gt;.&lt;/strong&gt; Especially, note that you are limited to send a reasonable amount of emails per day. If you have a &lt;code&gt;@gmail.com&lt;/code&gt; email address, you can send at most 500 emails/day while with a Google Workspace account, you sending limit hits 2000 emails per day. That's probably well enough for 99% of Gmail users, but it's good to have in mind those limits.&lt;/li&gt;
&lt;li&gt;If you are looking for more a advanced script, for example that let you send personalized emails (e.g. &lt;code&gt;Hi {{ firstname }}&lt;/code&gt;), I'd recommend you to use a Gmail mail merge tool such a &lt;a href="https://mailmeteor.com?utm_source=devto&amp;amp;utm_medium=blogpost&amp;amp;utm_campaign=mail-merge-gmail" rel="noopener noreferrer"&gt;Mailmeteor&lt;/a&gt; that does it for you. It already handles personalization, as well as dozens of features from attachments to aliases. Learn more about the &lt;a href="https://mailmeteor.com/features?utm_source=devto&amp;amp;utm_medium=blogpost&amp;amp;utm_campaign=mail-merge-gmail" rel="noopener noreferrer"&gt;features and benefits of Mailmeteor here&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This article is part of an extended guide on &lt;a href="https://mailmeteor.com/mail-merge-gmail/?utm_source=devto&amp;amp;utm_medium=blogpost&amp;amp;utm_campaign=mail-merge-gmail" rel="noopener noreferrer"&gt;Mail merge in Gmail (2021)&lt;/a&gt;. If you want to learn much more, go check it out!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to send a mail merge with Gmail?</title>
      <dc:creator>Corentin</dc:creator>
      <pubDate>Wed, 03 Mar 2021 10:59:05 +0000</pubDate>
      <link>https://dev.to/mailmeteor/how-to-create-a-mail-merge-for-gmail-2e0c</link>
      <guid>https://dev.to/mailmeteor/how-to-create-a-mail-merge-for-gmail-2e0c</guid>
      <description>&lt;p&gt;There are 2 ways to mail merge in Gmail. You can either use a Google add-on that will do the job for you or build your own &lt;a href="https://mailmeteor.com/mail-merge-gmail/script" rel="noopener noreferrer"&gt;mail merge script in Gmail&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We’ll cover both methods in  this guide. Even though we recommend using software built for that purpose which cover most issues and will probably save you time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 1: building a merge in Gmail using Google Apps Scripts
&lt;/h2&gt;

&lt;p&gt;As a developer, it's a good challenge to try to build your own Gmail mail merge without an add-on. And thankfully we’re going to use Google Apps Script, which makes it really easy to create Google add-ons.&lt;/p&gt;

&lt;p&gt;Google Developer Advocates have already released a great script to help us move forward with the code.&lt;/p&gt;

&lt;p&gt;Here is &lt;a href="https://github.com/googleworkspace/solutions/blob/master/mail-merge/src/Code.js" rel="noopener noreferrer"&gt;the latest version of the open-source code hosted on GitHub&lt;/a&gt; written by Martin Hawksey - &lt;a class="mentioned-user" href="https://dev.to/mhawksey"&gt;@mhawksey&lt;/a&gt;. We gonna look at it step-by-step just after.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Copyright Martin Hawksey 2020&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// Licensed under the Apache License, Version 2.0 (the "License"); you may not&lt;/span&gt;
&lt;span class="c1"&gt;// use this file except in compliance with the License.  You may obtain a copy&lt;/span&gt;
&lt;span class="c1"&gt;// of the License at&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;//     https://www.apache.org/licenses/LICENSE-2.0&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// Unless required by applicable law or agreed to in writing, software&lt;/span&gt;
&lt;span class="c1"&gt;// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT&lt;/span&gt;
&lt;span class="c1"&gt;// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the&lt;/span&gt;
&lt;span class="c1"&gt;// License for the specific language governing permissions and limitations under&lt;/span&gt;
&lt;span class="c1"&gt;// the License.&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * @OnlyCurrentDoc
*/&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Change these to match the column names you are using for email 
 * recipient addresses and email sent column.
*/&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RECIPIENT_COL&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Recipient&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EMAIL_SENT_COL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Email Sent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/** 
 * Creates the menu item "Mail Merge" for user to run scripts on drop-down.
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onOpen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ui&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SpreadsheetApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUi&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createMenu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mail Merge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Send Emails&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sendEmails&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addToUi&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Send emails from sheet data.
 * @param {string} subjectLine (optional) for the email draft message
 * @param {Sheet} sheet to read data from
*/&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendEmails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subjectLine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;SpreadsheetApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getActiveSheet&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// option to skip browser prompt if you want to use this code in other projects&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;subjectLine&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="nx"&gt;subjectLine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inputBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Mail Merge&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                                      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Type or copy/paste the subject line of the Gmail &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;draft message you would like to mail merge with:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                      &lt;span class="nx"&gt;Browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Buttons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OK_CANCEL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subjectLine&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cancel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;subjectLine&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt; 
    &lt;span class="c1"&gt;// if no subject line finish up&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// get the draft Gmail message to use as a template&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emailTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getGmailTemplateFromDrafts_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subjectLine&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// get the data from the passed sheet&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dataRange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDataRange&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// Fetch displayed values for each row in the Range HT Andrew Roberts &lt;/span&gt;
  &lt;span class="c1"&gt;// https://mashe.hawksey.info/2020/04/a-bulk-email-mail-merge-with-gmail-and-google-sheets-solution-evolution-using-v8/#comment-187490&lt;/span&gt;
  &lt;span class="c1"&gt;// @see https://developers.google.com/apps-script/reference/spreadsheet/range#getdisplayvalues&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dataRange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDisplayValues&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// assuming row 1 contains our column headings&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;heads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 

  &lt;span class="c1"&gt;// get the index of column named 'Email Status' (Assume header names are unique)&lt;/span&gt;
  &lt;span class="c1"&gt;// @see http://ramblings.mcpher.com/Home/excelquirks/gooscript/arrayfunctions&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emailSentColIdx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;heads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;EMAIL_SENT_COL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// convert 2d array into object array&lt;/span&gt;
  &lt;span class="c1"&gt;// @see https://stackoverflow.com/a/22917499/1027723&lt;/span&gt;
  &lt;span class="c1"&gt;// for pretty version see https://mashe.hawksey.info/?p=17869/#comment-184945&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;heads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{})));&lt;/span&gt;

  &lt;span class="c1"&gt;// used to record sent emails&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="c1"&gt;// loop through all the rows of data&lt;/span&gt;
  &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rowIdx&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="c1"&gt;// only send emails is email_sent cell is blank and not hidden by filter&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;EMAIL_SENT_COL&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msgObj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fillInTemplateFromObject_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;emailTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// @see https://developers.google.com/apps-script/reference/gmail/gmail-app#sendEmail(String,String,String,Object)&lt;/span&gt;
        &lt;span class="c1"&gt;// if you need to send emails with unicode/emoji characters change GmailApp for MailApp&lt;/span&gt;
        &lt;span class="c1"&gt;// Uncomment advanced parameters as needed (see docs for limitations)&lt;/span&gt;
        &lt;span class="nx"&gt;GmailApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;RECIPIENT_COL&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;msgObj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msgObj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;htmlBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;msgObj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="c1"&gt;// bcc: 'a.bbc@email.com',&lt;/span&gt;
          &lt;span class="c1"&gt;// cc: 'a.cc@email.com',&lt;/span&gt;
          &lt;span class="c1"&gt;// from: 'an.alias@email.com',&lt;/span&gt;
          &lt;span class="c1"&gt;// name: 'name of the sender',&lt;/span&gt;
          &lt;span class="c1"&gt;// replyTo: 'a.reply@email.com',&lt;/span&gt;
          &lt;span class="c1"&gt;// noReply: true, // if the email should be sent from a generic no-reply email address (not available to gmail.com users)&lt;/span&gt;
          &lt;span class="na"&gt;attachments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;emailTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attachments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;inlineImages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;emailTemplate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inlineImages&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="c1"&gt;// modify cell to record email sent date&lt;/span&gt;
        &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()]);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// modify cell to record error&lt;/span&gt;
        &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;EMAIL_SENT_COL&lt;/span&gt;&lt;span class="p"&gt;]]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// updating the sheet with new data&lt;/span&gt;
  &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;emailSentColIdx&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Get a Gmail draft message by matching the subject line.
   * @param {string} subject_line to search for draft message
   * @return {object} containing the subject, plain and html message body and attachments
  */&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getGmailTemplateFromDrafts_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subject_line&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// get drafts&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;drafts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GmailApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDrafts&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="c1"&gt;// filter the drafts that match subject line&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;drafts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;subjectFilter_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subject_line&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="c1"&gt;// get the message object&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMessage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="c1"&gt;// Handling inline images and attachments so they can be included in the merge&lt;/span&gt;
      &lt;span class="c1"&gt;// Based on https://stackoverflow.com/a/65813881/1027723&lt;/span&gt;
      &lt;span class="c1"&gt;// Get all attachments and inline image attachments&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allInlineImages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMessage&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getAttachments&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;includeInlineImages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;includeAttachments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;attachments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMessage&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getAttachments&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;includeInlineImages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;htmlBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBody&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 

      &lt;span class="c1"&gt;// Create an inline image object with the image name as key &lt;/span&gt;
      &lt;span class="c1"&gt;// (can't rely on image index as array based on insert order)&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;img_obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allInlineImages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;,{});&lt;/span&gt;

      &lt;span class="c1"&gt;//Regexp to search for all img string positions with cid&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imgexp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;img.*?src="cid:(.*?)".*?alt="(.*?)"[^&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;&amp;gt;]+&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;g&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;htmlBody&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matchAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imgexp&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;

      &lt;span class="c1"&gt;//Initiate the allInlineImages object&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inlineImagesObj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
      &lt;span class="c1"&gt;// built an inlineImagesObj from inline image matches&lt;/span&gt;
      &lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;inlineImagesObj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;img_obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]]);&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;subject_line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPlainBody&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;htmlBody&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; 
              &lt;span class="na"&gt;attachments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;attachments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;inlineImages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inlineImagesObj&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Oops - can't find Gmail draft&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Filter draft objects with the matching subject linemessage by matching the subject line.
     * @param {string} subject_line to search for draft message
     * @return {object} GmailDraft object
    */&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;subjectFilter_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subject_line&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMessage&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getSubject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;subject_line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Fill template string with data object
   * @see https://stackoverflow.com/a/378000/1027723
   * @param {string} template string containing {{}} markers which are replaced with data
   * @param {object} data object used to replace {{}} markers
   * @return {object} message replaced with data
  */&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fillInTemplateFromObject_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// we have two templates one for plain text and the html body&lt;/span&gt;
    &lt;span class="c1"&gt;// stringifing the object means we can do a global replace&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;template_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// token replacement&lt;/span&gt;
    &lt;span class="nx"&gt;template_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;template_string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/{{&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;{}&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+}}/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;escapeData_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;{}&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;  &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template_string&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Escape cell data to make JSON safe
   * @see https://stackoverflow.com/a/9204218/1027723
   * @param {string} str to escape JSON special characters from
   * @return {string} escaped string
  */&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;escapeData_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;str&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[\\]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\\\&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[\"]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\\&lt;/span&gt;&lt;span class="s1"&gt;"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[\/]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[\b]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[\f]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;f&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[\n]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[\r]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;r&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[\t]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;t&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Follow the guidelines below to understand how to mail merge in Gmail using Apps Script:&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Create a copy of the sample mail merge spreadsheet
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.google.com/spreadsheets/d/1EfjLuYGab8Xt8wCn4IokBIG0_W4tBtiU4vxl3Y7FPsA/copy" rel="noopener noreferrer"&gt;Open this demonstration spreadsheet&lt;/a&gt; and click on “Make a copy” to get your own copy.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. In your new spreadsheet
&lt;/h3&gt;

&lt;p&gt;Click on &lt;code&gt;Tools&lt;/code&gt; &amp;gt; &lt;code&gt;Script editor&lt;/code&gt; to open Google Apps Script. From there, you will see that a script is already tied to your spreadsheet. That's because you've made a copy of the previous spreadsheet!&lt;/p&gt;

&lt;p&gt;In the script editor, you can update the code as you wish. Changes will be reflected immediately.&lt;/p&gt;

&lt;p&gt;Back to copied spreadsheet, update the “Recipients” column with email addresses you would like to use in the mail merge. Replace the cells’ value under the “Recipients” column with your own email address for example.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Now, open Gmail to create a new draft email
&lt;/h3&gt;

&lt;p&gt;You can use personalized variables, like &lt;code&gt;{{ firstname }}&lt;/code&gt; which correspond to column names of the spreadsheet you just copied. This indicates text you’d like to be replaced with data from the copied spreadsheet.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Back in the copied spreadsheet, click on the custom menu item called “Mail Merge” and then click on “Send Emails”
&lt;/h3&gt;

&lt;p&gt;This item menu was created by the Apps Script project and will start the mail merge process.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. A dialog box appears for authorization. Read the authorization notice and continue
&lt;/h3&gt;

&lt;p&gt;Important note: The script we are using has been created and proofread by Google Apps Script teams. Always be super careful when authorizing scripts and third-party apps in general.&lt;/p&gt;

&lt;p&gt;When prompted enter or copy-paste the subject line used in your draft Gmail message. Then click OK.&lt;/p&gt;
&lt;h3&gt;
  
  
  6. Sending your emails
&lt;/h3&gt;

&lt;p&gt;You will see that the “Email Sent” column will update with the message status. Back in Gmail, check your Sent folder and review the emails the program just sent for you!&lt;/p&gt;



&lt;p&gt;Keep in mind that using a script is at your own risk. Check Gmail’s sending limit before sending large volumes of emails. Especailly, be aware that your account can get blocked by Gmail if your emailing activity seems unusual in the eyes of anti-spam filters.&lt;/p&gt;

&lt;p&gt;For these reasons, we would recommend using a mail merge solution such as Mailmeteor. Mailmeteor deals with all these aspects for you and ensures that your privacy remains protected.&lt;/p&gt;
&lt;h2&gt;
  
  
  Method 2: using a mail merge add-on like Mailmeteor
&lt;/h2&gt;

&lt;p&gt;We’ll start with a real-life example to show you how to do a mail merge from Gmail using a Google Sheets add-on. In this example, we’re using &lt;a href="https://mailmeteor.com?utm_source=devto&amp;amp;utm_medium=blogpost&amp;amp;utm_campaign=mail-merge-gmail" rel="noopener noreferrer"&gt;Mailmeteor&lt;/a&gt;, the best rated Google mail merge add-on.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Get Mailmeteor
&lt;/h3&gt;

&lt;p&gt;All you have to do is to &lt;a href="https://gsuite.google.com/marketplace/app/mailmeteor_mail_merge_for_gmail/1008170693301" rel="noopener noreferrer"&gt;install Mailmeteor from the Google Workspace Marketplace&lt;/a&gt;. The Worskpace Marketplace is a place where you can find all the apps compatible with your Google Suite. Mailmeteor is a tool that integrates with Gmail and Google Sheets to merge emails with Gmail.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmailmeteor.com%2Fassets%2Fimg%2Fblog%2Fmail-merge-gmail%2Ftutorial-mailmeteor-install.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmailmeteor.com%2Fassets%2Fimg%2Fblog%2Fmail-merge-gmail%2Ftutorial-mailmeteor-install.png" alt="Install Mailmeteor for Google Sheets"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Add contact in Google Sheets
&lt;/h3&gt;

&lt;p&gt;Once Mailmeteor is installed, &lt;a href="http://sheets.new/" rel="noopener noreferrer"&gt;open a Google Sheets spreadsheet&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First of all, you will need to add recipients to a Google Sheets spreadsheet. This spreadsheet will be the place where you store your contact list. You will also be able to track your campaign metrics from there.&lt;/p&gt;

&lt;p&gt;To create a mailing list you can either add your recipients manually or import contacts. To import contacts in Google Sheets, go to &lt;em&gt;Menu &amp;gt; File &amp;gt; Import&lt;/em&gt; and select your Excel or .csv file.&lt;/p&gt;

&lt;p&gt;Here’s a mail merge demo spreadsheet we’re going to use:&lt;/p&gt;


  &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmailmeteor.com%2Fassets%2Fimg%2Fblog%2Fmail-merge-gmail%2Ftutorial-spreadsheet-demo-mail-merge.png" alt="Demo spreadsheet to mail merge in Gmail"&gt;&lt;a href="https://docs.google.com/spreadsheets/d/1RAJPfeW8ehVYGpu6nziKuWmFbog8hJSlcTyKT7FO7iY/edit?usp=drive_web&amp;amp;ouid=110634960934586502748" rel="nofollow noreferrer noopener"&gt;Link to mail merge demo spreadsheet&lt;/a&gt;
  


&lt;p&gt;Note: when opening Mailmeteor for the first time, you will be guided through a quick onboarding tutorial. A demo spreadsheet like this one will be created for you.&lt;/p&gt;

&lt;p&gt;Let’s breakdown how your spreadsheet should look like:&lt;/p&gt;

&lt;p&gt;➤ &lt;strong&gt;Add column headers on the 1st row&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Mailmeteor will pull the information from your spreadsheet to personalize your emails. Each column represents a personalized field. This field will be replaced in your email template.&lt;/p&gt;

&lt;p&gt;In our example, we have 4 columns named: firstname, email, company, postscriptum.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmailmeteor.com%2Fassets%2Fimg%2Fblog%2Fmail-merge-gmail%2Ftutorial-spreadsheet-demo-mail-merge.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmailmeteor.com%2Fassets%2Fimg%2Fblog%2Fmail-merge-gmail%2Ftutorial-spreadsheet-demo-mail-merge.png" alt="Demo spreadsheet to mail merge in Gmail"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add as many columns as you want and pick any column header name you want. Make sure you have a column named “email”.&lt;/p&gt;

&lt;p&gt;➤ &lt;strong&gt;Fill the columns with your recipients’ information&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fill your spreadsheet with your recipients’ info. Ensure all email cells are filled with valid email addresses. Apart from the emails, you can leave some cells blank, that is fine! In the example below, some recipients will get a Post Scriptum whereas others won't.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmailmeteor.com%2Fassets%2Fimg%2Fblog%2Fmail-merge-gmail%2Ftutorial-spreadsheet-demo-add-columns.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmailmeteor.com%2Fassets%2Fimg%2Fblog%2Fmail-merge-gmail%2Ftutorial-spreadsheet-demo-add-columns.gif" alt="Fill spreadsheet cells to mail merge in Mailmeteor"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Open Mailmeteor from the Add-ons menu in Google Sheets
&lt;/h3&gt;

&lt;p&gt;Once your contact list is ready, open Mailmeteor. To open Mailmeteor go to the menu and select Add-ons&amp;gt; Mailmeteor &amp;gt; Open Mailmeteor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmailmeteor.com%2Fassets%2Fimg%2Fblog%2Fmail-merge-gmail%2Ftutorial-mailmeteor-addon.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmailmeteor.com%2Fassets%2Fimg%2Fblog%2Fmail-merge-gmail%2Ftutorial-mailmeteor-addon.png" alt="Mailmeteor add-on to mail merge in Google Sheets"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the Mailmeteor interface. It tells you how many emails you can send per day and details related to your campaign. Next, we are going to compose the template that will be used for the mail merge.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Compose a new email template
&lt;/h3&gt;

&lt;p&gt;Click on the “Create new template” button. This will open an editor in which you can compose your email. The Mailmeteor editor is the exact same as Gmail, you will find all the actions you need to customize your email.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmailmeteor.com%2Fassets%2Fimg%2Fblog%2Fmail-merge-gmail%2Ftutorial-mailmeteor-compose-template.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmailmeteor.com%2Fassets%2Fimg%2Fblog%2Fmail-merge-gmail%2Ftutorial-mailmeteor-compose-template.gif" alt="Create a new template in Mailmeteor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we’re going to personalize your email. Personalizing emails is important as it helps make your recipients feel unique when they receive your emails. Using personalization will also dramatically improve your opening rates - and thus the replies you will get.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Personalize your emails
&lt;/h3&gt;

&lt;p&gt;A mail merge transforms a standard email template into a personalized email copy. It’s done by replacing variables fields within the template with the content from your spreadsheet.&lt;/p&gt;

&lt;p&gt;To insert a variable it’s easy: add variables using double brackets like this &lt;code&gt;{{ firstname }}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here is a template you can copy-paste:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;When adding a variable, always make sure that it matches a header in your spreadsheet.&lt;/p&gt;

&lt;p&gt;Once you are satisfied with your template, click the “&lt;strong&gt;Save&lt;/strong&gt;” button.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Preview emails before sending
&lt;/h3&gt;

&lt;p&gt;Mailmeteor offers a preview feature that is super helpful to review emails before sending. The preview mode gives you a glimpse of the actual output of your email once personalized for each recipient.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmailmeteor.com%2Fassets%2Fimg%2Fblog%2Fmail-merge-gmail%2Ftutorial-mailmeteor-preview-mail-merge.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmailmeteor.com%2Fassets%2Fimg%2Fblog%2Fmail-merge-gmail%2Ftutorial-mailmeteor-preview-mail-merge.gif" alt="Preview mail merge before sending"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also send a test email to yourself. Testing your emails on several devices is a best practice. This will ensure your emails will display correctly in most situations.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Send your mail merge
&lt;/h3&gt;

&lt;p&gt;Ready for take-off? It’s time to send your mailmerge campaign.&lt;/p&gt;

&lt;p&gt;We know that sending your mail merge can be a bit daunting at first. No worries though, if you follow these steps, everything will be alright!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmailmeteor.com%2Fassets%2Fimg%2Fblog%2Fmail-merge-gmail%2Ftutorial-mailmeteor-sending-mail-merge.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmailmeteor.com%2Fassets%2Fimg%2Fblog%2Fmail-merge-gmail%2Ftutorial-mailmeteor-sending-mail-merge.gif" alt="Sending a mail merge with Mailmeteor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;✨ That's it! You are now ready to mail merge emails with Gmail using an add-on such as Mailmeteor ✨&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Here's a real-life example. Watch this teacher use Mailmeteor to mail merge emails to his students:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/hfy5UKw36nA"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Now it’s your turn
&lt;/h2&gt;

&lt;p&gt;That’s it for this guide to mail merge in Gmail. We hope you now better understand this simple yet incredibly powerful tool called mail merge.&lt;/p&gt;

&lt;p&gt;This guide is part of an extended guide on &lt;a href="https://mailmeteor.com/mail-merge-gmail/?utm_source=devto&amp;amp;utm_medium=blogpost&amp;amp;utm_campaign=mail-merge-gmail" rel="noopener noreferrer"&gt;Mail merge in Gmail (2021)&lt;/a&gt;. If you want to learn much more, go check it out!&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>mailmerge</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>⚡️ How to call an OAuth based API in React?</title>
      <dc:creator>Corentin</dc:creator>
      <pubDate>Wed, 02 Sep 2020 15:43:19 +0000</pubDate>
      <link>https://dev.to/mailmeteor/how-to-make-api-calls-in-react-g1e</link>
      <guid>https://dev.to/mailmeteor/how-to-make-api-calls-in-react-g1e</guid>
      <description>&lt;p&gt;Do you know what Facebook, Google, GitHub, and thousands more APIs have in common? They use OAuth to authenticate requests.&lt;/p&gt;

&lt;p&gt;OAuth, especially OAuth 2.0, is now everywhere. It's a very powerful authentication framework that powers up developers to have granularity over the data that it needs.&lt;/p&gt;

&lt;h1&gt;
  
  
  React + OAuth = 🤔
&lt;/h1&gt;

&lt;p&gt;When it comes to OAuth-based API, your React app is not well-suited to send requests. You just can't hide your API keys deep into your codebase. Someone will easily find it.&lt;/p&gt;

&lt;p&gt;What you need to do is to set up some backend service, that proxies requests to the third-party API. It can be fun to do, but it's a long process for a quick API call.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For example, you will need a backend to protect your API keys. Declare routes, understand packages (&lt;em&gt;like Passport.js&lt;/em&gt;), proxying requests, and dozens of more actions. It doesn't have to be so hard.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Today, I'll showcase an open-source project that I'm actively contributing to. It's called &lt;a href="https://github.com/Bearer/Pizzly"&gt;Pizzly&lt;/a&gt; and it helps a lot with using API from a single page application.&lt;/p&gt;

&lt;p&gt;Let's see how it looks like with a simple demo:&lt;br&gt;
&lt;iframe src="https://codesandbox.io/embed/rq78z?view=preview"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Curious about how you can do it on your application? Here's a full guide.&lt;/p&gt;

&lt;h1&gt;
  
  
  The React skeleton 🦴
&lt;/h1&gt;

&lt;p&gt;To learn how to make API calls to an API, we first need a React skeleton. And the least that we need is an app that consumes an API endpoint using OAuth2.&lt;/p&gt;

&lt;p&gt;As you probably have a GitHub account, we will use that API, but you can easily switch to any other API that uses OAuth2 (Slack, Salesforce, ...) or OAuth1 (Twitter, Trello, ...).&lt;/p&gt;

&lt;p&gt;Here's how the application will look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Pizzly&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pizzly-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Profile&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Profile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setProfile&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"App"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Hello!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Click the button bellow to retrieve your GitHub profile using&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"_blank"&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"noopener noreferrer"&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://github.com/Bearer/Pizzly"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          Pizzly
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        .
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Retrieve your GitHub profile&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Profile&lt;/span&gt; &lt;span class="na"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a very basic React application that renders the user's profile as a plain JSON when it has been fetched. Otherwise, it asks the user to connect to GitHub.&lt;/p&gt;

&lt;h1&gt;
  
  
  The authentication 🔐
&lt;/h1&gt;

&lt;p&gt;Here, we gonna use &lt;a href="https://github.com/Bearer/Pizzly"&gt;Pizzly&lt;/a&gt;, the open-source project I told you about a few lines above. &lt;/p&gt;

&lt;p&gt;It provides a &lt;code&gt;.connect()&lt;/code&gt; method that triggers an authentication flow from your frontend, which you can handle with callbacks. No need to create a redirect URL, deal with backend, etc.&lt;/p&gt;

&lt;p&gt;Let's see how to update the skeleton above to use with Pizzly.&lt;/p&gt;

&lt;p&gt;First, we need to initialize Pizzly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Initialize Pizzly&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pizzly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Pizzly&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PIZZLY_HOSTNAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;publishableKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PIZZLY_PUBLISHABLE_KEY&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;github&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pizzly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;integration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;github&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;setupId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PIZZLY_SETUP_ID_GITHUB_DEMO_APP&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we add a new &lt;code&gt;connect()&lt;/code&gt; method to trigger the authentication flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * The connect method lets us authenticate a user
   * to our GitHub application (i.e. the OAuth dance)
   */&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;connect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;github&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;authId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sucessfully connected!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;fetchProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. A few lines of code in your application and you are ready to handle any OAuth based API 💪.&lt;/p&gt;

&lt;h1&gt;
  
  
  The configuration 🤖
&lt;/h1&gt;

&lt;p&gt;Pizzly is a self-hosted OAuth manager. This means that you need to host it somewhere, for example on Heroku (it takes 30 seconds). Once hosted, you can access Pizzly's dashboard, which is where you configure your GitHub integration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wr_62uUl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/Bearer/Pizzly/master/views/assets/img/docs/pizzly-dashboard-all-apis.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wr_62uUl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/Bearer/Pizzly/master/views/assets/img/docs/pizzly-dashboard-all-apis.png" alt="Pizzly dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To deploy your own Pizzly instance right now, click on any of the following button:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Heroku&lt;/th&gt;
&lt;th&gt;Platform.sh&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://heroku.com/deploy?template=https://github.com/Bearer/Pizzly" rel="nofollow"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--26lElwvG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.herokucdn.com/deploy/button.svg" alt="Deploy to Heroku"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://console.platform.sh/projects/create-project/?template=https://github.com/Bearer/Pizzly&amp;amp;utm_campaign=deploy_on_platform?utm_medium=button&amp;amp;utm_source=affiliate_links&amp;amp;utm_content=https://github.com/Bearer/Pizzly" rel="nofollow"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D7yKEFCs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://platform.sh/images/deploy/deploy-button-lg-blue.svg" alt="Deploy with Platform.sh"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Then, select the GitHub API. And configure it by saving your application's client ID, client credentials and scopes. You will get them from GitHub by following &lt;a href="https://docs.github.com/en/developers/apps/creating-an-oauth-app"&gt;this guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once your Pizzly instance is created and you have configured a GitHub application, edit your React application with the following values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pizzly environment variables, make sure to replace&lt;/span&gt;
&lt;span class="c1"&gt;// these with those of your own Pizzly instance&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PIZZLY_HOSTNAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PIZZLY_PUBLISHABLE_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PIZZLY_SETUP_ID_GITHUB_DEMO_APP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The least that you need is &lt;code&gt;PIZZLY_HOSTNAME&lt;/code&gt;. The two others are optional.&lt;/p&gt;

&lt;h1&gt;
  
  
  An authenticated API request 🎉
&lt;/h1&gt;

&lt;p&gt;Alright, we have already spend a few minutes on the configuration. Let's move back to funny things.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.github.com/en/rest/reference/users#get-the-authenticated-user"&gt;GitHub API provides a handy endpoint (&lt;code&gt;/user&lt;/code&gt;)&lt;/a&gt; to retrieve the profile of the authenticated user. This endpoint uses OAuth authentication, so it looks like a good use case.&lt;/p&gt;

&lt;p&gt;Let's add a new method to our application to do that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * The fetchProfile method retrieves the authenticated
   * user profile on GitHub (the request is proxied through Pizzly)
   */&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchProfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;github&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;em&gt;voilà&lt;/em&gt;!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;NB: To authenticate the request, we have provided an &lt;code&gt;authId&lt;/code&gt;. It's like a user identity for that particular API. It was generated (and saved) with the &lt;code&gt;connect()&lt;/code&gt; method.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  What's next? 💡
&lt;/h1&gt;

&lt;p&gt;You now know how to authenticate a user towards any OAuth based API using React. If you prefer Vue.js, &lt;a href="https://dev.to/bearer/how-to-use-an-oauth-based-api-in-vue-js-1elo"&gt;the same tutorial is available for Vue.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It's easily adaptable to &lt;a href="https://github.com/Bearer/Pizzly/wiki/Supported-APIs"&gt;all the most famous APIs&lt;/a&gt;. No need to create backend routes or understand every single concept of OAuth. Pizzly takes care of that for you (and for the experts, Pizzly automatically refreshes the &lt;code&gt;access_token&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Again, &lt;a href="https://codesandbox.io/s/pizzly-github-react-demo-rq78z"&gt;have a look at the CodeSandbox&lt;/a&gt; to have a full understanding of the code and share your thoughts/questions in the comments below.&lt;/p&gt;

&lt;p&gt;And if you've liked this tutorial, please add a star to Pizzly on GitHub. Here's the link: &lt;a href="https://github.com/Bearer/Pizzly"&gt;https://github.com/Bearer/Pizzly&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>pizzly</category>
    </item>
    <item>
      <title>Request for Node.js has been deprecated</title>
      <dc:creator>Corentin</dc:creator>
      <pubDate>Wed, 19 Feb 2020 15:05:33 +0000</pubDate>
      <link>https://dev.to/mailmeteor/request-for-node-js-has-been-deprecated-422</link>
      <guid>https://dev.to/mailmeteor/request-for-node-js-has-been-deprecated-422</guid>
      <description>&lt;p&gt;The breaking news landed a few days ago, with &lt;a href="https://github.com/request/request/commit/aded7e4f8e57f6f33cf39d65634bfb822bfcb2c8"&gt;a simple commit&lt;/a&gt; entitled "doc: note full deprecation".&lt;/p&gt;

&lt;p&gt;That's right! &lt;strong&gt;&lt;a href="https://github.com/request/request"&gt;&lt;code&gt;request&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;, one of the top and oldest npm packages, has been deprecated by &lt;a class="mentioned-user" href="https://dev.to/mikeal"&gt;@mikeal&lt;/a&gt; its creator.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--b3ihJLT9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/tqe5pjgl4kto4q8hczto.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--b3ihJLT9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/tqe5pjgl4kto4q8hczto.png" alt="Screenshot of the deprecation notice on npm" width="880" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As of writing, over 40K packages rely on &lt;code&gt;request&lt;/code&gt;. In fact, it's the 4th &lt;a href="https://www.npmjs.com/browse/depended"&gt;most depended upon package on npm&lt;/a&gt;. And it has a whopping 18.5 million weekly downloads.&lt;/p&gt;

&lt;p&gt;For the most familiar with the package's development, there's no news here. &lt;code&gt;request&lt;/code&gt; was in maintenance mode since March 2019 (see issue &lt;a href="https://github.com/request/request/issues/3142"&gt;#3142&lt;/a&gt;). And deprecating the package is just one step further.&lt;/p&gt;

&lt;p&gt;Still, it is, I believe, a truly courageous and clever step toward the future of the Node.js (and more broadly the JavaScript) ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's review what led to this situation.
&lt;/h2&gt;

&lt;p&gt;Without paraphrasing too much &lt;a href="https://github.com/request/request/issues/3142"&gt;@mikeal statement back from March 2019&lt;/a&gt;, here are the key points that led to this deprecation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Back in 2009, &lt;code&gt;request&lt;/code&gt; was one of the first modules ever created for the Node.js ecosystem.&lt;/li&gt;
&lt;li&gt;For a few years, &lt;code&gt;request&lt;/code&gt; and Node.js evolved together, each learning from the other.&lt;/li&gt;
&lt;li&gt;And as one of the very first modules listed on npm, &lt;code&gt;request&lt;/code&gt; quickly became one of the most downloaded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"The place request has in the Node.js ecosystem is no longer one of an innovator but of an incumbent."&lt;/em&gt; &lt;a href="https://github.com/request/request/issues/3142"&gt;@mikeal&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's right that most tutorials, even here on DEV, use &lt;code&gt;request&lt;/code&gt; as the per-default HTTP client - while there are dozens of very good alternatives.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"The best thing for these new modules is for &lt;code&gt;request&lt;/code&gt; to slowly fade away, eventually becoming just another memory of that legacy stack."&lt;/em&gt; &lt;a class="mentioned-user" href="https://dev.to/mikeal"&gt;@mikeal&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the very first time that I see such a humble position. It's truly clever. And while it's going to require 40K+ packages to be upgraded, that's totally worth it. &lt;/p&gt;

&lt;p&gt;What do you think?&lt;/p&gt;

</description>
      <category>request</category>
      <category>npm</category>
      <category>node</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to send data to a Google spreadsheet, in 2 seconds?</title>
      <dc:creator>Corentin</dc:creator>
      <pubDate>Fri, 13 Sep 2019 09:00:26 +0000</pubDate>
      <link>https://dev.to/mailmeteor/how-to-send-data-to-a-google-spreadsheet-in-2-seconds-1h0</link>
      <guid>https://dev.to/mailmeteor/how-to-send-data-to-a-google-spreadsheet-in-2-seconds-1h0</guid>
      <description>&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;em&gt;The "Push to GSheet" service used in this article is no longer active, but you can use &lt;a href="https://github.com/Bearer/Pizzly/" rel="noopener noreferrer"&gt;Pizzly&lt;/a&gt; to host your own version.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I'm a huge fan of Google APIs. I use a lot of them on a daily basis. Whether in my code or through the infinite number of services that rely on it.&lt;/p&gt;

&lt;p&gt;Recently, I was running a scraping script and quickly found myself doing some horrible copy/pasting into a Google Sheet. I thought that there should be a better way to dynamically push data into a spreadsheet:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/lkdgvyS7701v93PeCF/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/lkdgvyS7701v93PeCF/giphy.gif" alt="GIF"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Having a quick look at the Google Sheets API reference, I found an &lt;code&gt;/append&lt;/code&gt; endpoint that would do the work nicely. 30 minutes later, I was sipping lemonade while my script was running in the background 😎&lt;/p&gt;

&lt;p&gt;If I wasn't in love with Google APIs, I'd certainly have moved on to something else. But wait, &lt;code&gt;20 minutes&lt;/code&gt; to send data to a spreadsheet? Come on! Let's try to make it as simple as searching on Google.&lt;/p&gt;

&lt;h1&gt;
  
  
  What do we need?
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;First, understanding the API.&lt;/strong&gt; As said just before, Google Sheets provides a &lt;a href="https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/append" rel="noopener noreferrer"&gt;handy endpoint &lt;code&gt;/append&lt;/code&gt;&lt;/a&gt; to push any type of data. All that it asks for, are values formatted as a two-dimensional &lt;code&gt;array&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="c1"&gt;// First row&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="c1"&gt;// Second row&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;B1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;B2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;B3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="c1"&gt;// Third row... &lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Data will always be appended to the sheet. So if the sheet has some values on row #1, new values will be added on row #2 (and so-on).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next, handling the authentication.&lt;/strong&gt; Like most APIs that give access to users' data, the Google Sheets API uses OAuth2. It's an authentication schema that is both very powerful and... complicated to setup. &lt;/p&gt;

&lt;p&gt;What's interesting for us, is that each request to the Google Sheets API requires an &lt;code&gt;access_token&lt;/code&gt; - a long string, unguessable, that certifies the request is made on behalf of a user.&lt;/p&gt;

&lt;p&gt;Google provides a &lt;a href="https://developers.google.com/oauthplayground/" rel="noopener noreferrer"&gt;playground&lt;/a&gt; to retrieve one easily. But I'm going to use a service called &lt;a href="https://github.com/Bearer/Pizzly" rel="noopener noreferrer"&gt;Pizzly&lt;/a&gt; that handles the OAuth-dance securely for me and will handle refresh token, something that the playground doesn't (&lt;em&gt;disclaimer: I helped built Pizzly&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anything else?&lt;/strong&gt; We are just missing on which spreadsheet the data shall be appended. That's the &lt;a href="https://developers.google.com/sheets/api/guides/concepts#spreadsheet_id" rel="noopener noreferrer"&gt;&lt;code&gt;spreadsheetID&lt;/code&gt;&lt;/a&gt; as per the documentation. Each spreadsheet has a unique ID that we can find by looking into the &lt;code&gt;URL&lt;/code&gt;:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;p&gt;&lt;a href="https://docs.google.com/spreadsheets/d/%7B%7BspreadsheetId%7D%7D/edit#gid=0" rel="noopener noreferrer"&gt;https://docs.google.com/spreadsheets/d/{{spreadsheetId}}/edit#gid=0&lt;/a&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Show me the code!&lt;br&gt;
&lt;/h1&gt;

&lt;p&gt;Wrapping it up, it took 10 lines of code to actually send data to any spreadsheet.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="c1"&gt;// 1. Run npm install node-fetch&lt;/span&gt;&lt;br&gt;
&lt;span class="c1"&gt;// 2. Import an HTTP client&lt;/span&gt;&lt;br&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node-fetch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="c1"&gt;// 3. Declare spreadsheet and values to append&lt;/span&gt;&lt;br&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;spreadsheetId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SPREADSHEET_ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;br&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;firstname&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lastname&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="c1"&gt;// 4. Send data with a POST request&lt;/span&gt;&lt;br&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&lt;a href="https://pushtogsheet.herokuapp.com" rel="noopener noreferrer"&gt;https://pushtogsheet.herokuapp.com&lt;/a&gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&lt;code&gt;valueInputOption=RAW&amp;amp;amp;pizzly_pkey=pope8Qy8qfYyppnHRMgLMpQ8MuEUKDGeyhfGCj&lt;/code&gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;br&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&lt;code&gt;/proxy/google-sheets/spreadsheets/&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;${&amp;lt;/span&amp;gt;&amp;lt;span class="nx"&amp;gt;spreadsheetId&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span class="s2"&amp;gt;/values/A1:append?&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;${&amp;lt;/span&amp;gt;&amp;lt;span class="nx"&amp;gt;query&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span class="s2"&amp;gt;&lt;/code&gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;&lt;br&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Pizzly-Auth-Id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CONNECT_FIRST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;br&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;&lt;br&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;br&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  How can I use it?&lt;br&gt;
&lt;/h1&gt;

&lt;p&gt;&lt;del&gt;I did my best to make pushing data to &lt;em&gt;gsheet&lt;/em&gt; as easy as googling, and &lt;a href="https://community.bearer.sh/pushtogsheet" rel="noopener noreferrer"&gt;crafted a dedicated website for that&lt;/a&gt;:&lt;/del&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.bearer.sh/pushtogsheet" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpix.watch%2FS1Vdqk%2FN66g68.png" alt="Push To GSheet website"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The "Push to GSheet" service used in this article is no longer active, but you can use &lt;a href="https://github.com/Bearer/Pizzly/" rel="noopener noreferrer"&gt;Pizzly&lt;/a&gt; to host your own version.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;PS: it's 100% free and open source&lt;/em&gt; ✌️&lt;/p&gt;

</description>
      <category>growth</category>
      <category>hack</category>
      <category>googleapis</category>
      <category>pizzly</category>
    </item>
    <item>
      <title>How to create a bunch of Google Alerts, in 3 minutes?</title>
      <dc:creator>Corentin</dc:creator>
      <pubDate>Tue, 27 Aug 2019 16:47:14 +0000</pubDate>
      <link>https://dev.to/mailmeteor/how-to-create-a-bunch-of-google-alerts-in-3-minutes-54n</link>
      <guid>https://dev.to/mailmeteor/how-to-create-a-bunch-of-google-alerts-in-3-minutes-54n</guid>
      <description>&lt;p&gt;&lt;a href="https://www.google.com/alerts" rel="noopener noreferrer"&gt;Google Alerts&lt;/a&gt; is a very handy and powerful service to stay informed of what's happening in a particular field. &lt;/p&gt;

&lt;p&gt;I'm a huge fan of this service and I create dozens of new alerts every week. Recently, I wanted to add 100+ Google Alerts to being informed about APIs that we're working with at &lt;a href="https://www.bearer.sh" rel="noopener noreferrer"&gt;Bearer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Unfortunately, Google Alerts doesn't provide an API. And I didn't felt ok to share my Google credentials (email + password) to &lt;a href="https://www.npmjs.com/package/google-alerts-api" rel="noopener noreferrer"&gt;the&lt;/a&gt; &lt;a href="https://medium.com/t-brian-jones-engineering/google-alerts-api-client-for-creating-alerts-in-bulk-2f20a17f087f" rel="noopener noreferrer"&gt;available&lt;/a&gt; &lt;a href="https://pypi.org/project/google-alerts/" rel="noopener noreferrer"&gt;libraries&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do we need?
&lt;/h2&gt;

&lt;p&gt;First, a list of keywords that we want to add alerts on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Here's my list of keywords to add Google Alerts on;&lt;/span&gt;
&lt;span class="c1"&gt;// Change it with whatever you want to be informed of.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keywords&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GitHub API&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Google Alerts API&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dev.to API&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, head to &lt;a href="https://www.google.com/alerts" rel="noopener noreferrer"&gt;Google Alerts&lt;/a&gt; so we will learn how it works behind the scene:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpix.watch%2FDO6exB%2FBZ2pVW.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpix.watch%2FDO6exB%2FBZ2pVW.png" alt="Google Alerts homepage"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm using Google Chrome, but that should work just fine with Safari or Firefox.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a Google Alert with JS
&lt;/h2&gt;

&lt;p&gt;On Google Alerts homepage, open the developer tools Alt+Command+J (on Mac) or Ctrl+Shit+J (on Windows), then open the &lt;code&gt;Network&lt;/code&gt; tab. You should see something like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpix.watch%2FQt8-II%2F_LTCvL.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpix.watch%2FQt8-II%2F_LTCvL.png" alt="Network request opened on Google Alerts homepage"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now create a sample alert using &lt;code&gt;dev.to&lt;/code&gt; as the keyword. The network tab will show a request to the &lt;code&gt;/create&lt;/code&gt; endpoint. Use &lt;code&gt;Copy as fetch&lt;/code&gt; to see what's inside that request:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpix.watch%2FnoiPEk%2FxR3iqw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpix.watch%2FnoiPEk%2FxR3iqw.png" alt="Request to the /create endpoint"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are almost done 🙌 If you paste that into the console, you will have something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Code has been prettified&lt;/span&gt;
&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.google.com/alerts/create?x=ABJHsmWAbcU-xxxxxxxxxxxxxxxxxxxxx&amp;amp;hl=us&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;credentials&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;include&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;headers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accept&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accept-language&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cache-control&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-cache&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/x-www-form-urlencoded;charset=UTF-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pragma&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-cache&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sec-fetch-mode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cors&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sec-fetch-site&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;same-origin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-client-data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;xxxxxxxxxxxxxxxxxxxxx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;referrer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.google.com/alerts?hl=us&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;referrerPolicy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-referrer-when-downgrade&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// The dev.to keyword is passed ==================== right here ∨∨∨&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;params=%5Bnull%2C%5Bnull%2Cnull%2Cnull%2C%5Bnull%2C%22dev.to%22%2C%22com%22%2C%5Bnull%2C%22en%22%2C%22US%22%5D%2Cnull%2Cnull%2Cnull%2C0%2C1%5D%2Cnull%2C3%2C%5B%5Bnull%2C1%2C%22corentin%40bearer.sh%22%2C%5Bnull%2Cnull%2C10%5D%2C2%2C%22en-US%22%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2C%220%22%2Cnull%2Cnull%2C%22AB2xxxxxxxxxxx%22%5D%5D%5D%5D&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;method&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cors&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you might see, the &lt;code&gt;dev.to&lt;/code&gt; keyword is passed into the body. Changing it to something else, will let us automatically add a new Google Alert 🥳 &lt;/p&gt;

&lt;h2&gt;
  
  
  A script that creates Google Alerts in bulk
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Replace with your keywords list&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;keywords&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GitHub API&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Google Alerts API&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Dev.to API&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;addAlert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Retrieve the keyword to work with&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keyword&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keywords&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="c1"&gt;// Stop the script if there's no keyword&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;keywords&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Adding &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// 1. Replace the line below with your own fetch (see Copy as fetch above)&lt;/span&gt;
  &lt;span class="c1"&gt;// 2. Replace `dev.to` with `${keyword}`&lt;/span&gt;
  &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/*...*/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Exponentially delay the next request,&lt;/span&gt;
  &lt;span class="c1"&gt;// to avoid rate limit on Google.&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;addAlert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;addAlert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I recommend adding it as a snippet on your Google Chrome (&lt;a href="https://developers.google.com/web/tools/chrome-devtools/javascript/snippets" rel="noopener noreferrer"&gt;learn how to do it right here&lt;/a&gt;).&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>google</category>
      <category>alerts</category>
      <category>automatization</category>
    </item>
  </channel>
</rss>
