<?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: Paul Oms</title>
    <description>The latest articles on DEV Community by Paul Oms (@ehlo_250).</description>
    <link>https://dev.to/ehlo_250</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%2Fuser%2Fprofile_image%2F714793%2F8b2ed3f2-6036-414c-8431-14db374c6db8.jpg</url>
      <title>DEV Community: Paul Oms</title>
      <link>https://dev.to/ehlo_250</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ehlo_250"/>
    <language>en</language>
    <item>
      <title>How to create a terminal simulation in the browser 👩‍💻🧑‍💻</title>
      <dc:creator>Paul Oms</dc:creator>
      <pubDate>Tue, 31 Oct 2023 11:37:09 +0000</pubDate>
      <link>https://dev.to/ehlo_250/how-to-create-a-terminal-simulation-in-the-browser-3l54</link>
      <guid>https://dev.to/ehlo_250/how-to-create-a-terminal-simulation-in-the-browser-3l54</guid>
      <description>&lt;p&gt;When demonstrating an API or similar service, your landing page should show how it works! You can &lt;a href="https://blog.mailpace.com/blog/adding-code-syntax-highlighting/" rel="noopener noreferrer"&gt;easily use something like PrismJS to add syntax highlighting to your code snippets&lt;/a&gt;, which looks neat. But what about creating something a bit more compelling for the Hero unit of your landing page?&lt;/p&gt;

&lt;p&gt;It needs to be simple and clear, and not necessarily syntactically correct, but it should look like a terminal, complete with a blinking cursor and simulated text entry. Something like this:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbejbff0uzrdnhf88xp9m.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbejbff0uzrdnhf88xp9m.png" alt="Simple terminal simulation in the browser"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the the help of &lt;a href="https://github.com/mattboldt/typed.js/" rel="noopener noreferrer"&gt;TypedJS&lt;/a&gt; and &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;TailwindCSS&lt;/a&gt; this is actually really easy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependencies
&lt;/h2&gt;

&lt;p&gt;Let's start by including TailwindCSS and Typed.js in our HTML &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; element:&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;head&amp;gt;&lt;/span&gt;
    ...
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.tailwindcss.com"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/typed.js@2.0.16/dist/typed.umd.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Note that you might want to use a pre-processor/build step or similar in production, and load them async etc.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Terminal window
&lt;/h2&gt;

&lt;p&gt;We'll start by creating a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; element with a class of &lt;code&gt;terminal-window&lt;/code&gt; and a &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; element with a class of &lt;code&gt;terminal-output&lt;/code&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"terminal-window"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;pre&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"terminal-output"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Then let's style it a little with TailwindCSS, and add a close button, so it looks a bit like a terminal:&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"terminal-window mx-2 w-full xl:w-4/5 p-6 text-base sm:text-sm md:text-base rounded-md shadow-2xl bg-gray-800 max-h-80"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"relative"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"absolute -top-5 -right-5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="c"&gt;&amp;lt;!-- Close button --&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Close"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-gray-600 w-8 h-8 rounded-full flex items-center justify-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"none"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 24 24"&lt;/span&gt; &lt;span class="na"&gt;stroke=&lt;/span&gt;&lt;span class="s"&gt;"currentColor"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w-4 h-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;path&lt;/span&gt; &lt;span class="na"&gt;stroke-linecap=&lt;/span&gt;&lt;span class="s"&gt;"round"&lt;/span&gt; &lt;span class="na"&gt;stroke-linejoin=&lt;/span&gt;&lt;span class="s"&gt;"round"&lt;/span&gt; &lt;span class="na"&gt;stroke-width=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt; &lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M6 18L18 6M6 6l12 12"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/path&amp;gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;pre&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-gray-300 whitespace-pre"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"terminal-output"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;
&lt;h2&gt;
  
  
  Sprinkle a bit of JavaScript
&lt;/h2&gt;

&lt;p&gt;First initalize TypedJS, targeting &lt;code&gt;terminal-output&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;typed&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;Typed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#terminal-output&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;strings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;textToType&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;typeSpeed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;loop&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="na"&gt;cursorChar&lt;/span&gt;&lt;span class="p"&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="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;Next we'll write the text we want to show, it's a bit awkward but it's kind of like a mini Domain Specific Language:&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;textToShow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$ curl &amp;lt;span class=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;text-white&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;https://app.mailpace.com/api/v1/send&amp;lt;/span&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt; -H MailPace-Server-Token: a3c4-efg6 &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt; -d {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    from: awesome@developer.com,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    to: important@users.com,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    subject: Woah, MailPace Rocks!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;} &lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;span class=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;text-gray-500&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;Sending...&amp;lt;/span&amp;gt;`&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;^250&amp;lt;span class=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;text-green-200&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;✓ Email Sent!&amp;lt;/span&amp;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;ul&gt;
&lt;li&gt;^ followed by a number means to pause by that number of milliseconds, e.g. while simulating a network call&lt;/li&gt;
&lt;li&gt;Anything wrapped in backticks is output from the terminal&lt;/li&gt;
&lt;li&gt;\n is a line break&lt;/li&gt;
&lt;li&gt;We we can embed &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; elements to give us basic syntax highlighting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that this curl example isn't working code, we just want to represent a simplified version for our viewers to understand what's going on. If we add all the pieces needed for it to work, it's not as clear what's going on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add some animation
&lt;/h2&gt;

&lt;p&gt;Not strictly necessary, but let's add a little bounce when our users open the site, to kick start the typing:&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;style&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;softBounce&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="err"&gt;20&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="err"&gt;50&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="err"&gt;80&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="err"&gt;40&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-30px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="err"&gt;60&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;translateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;-5px&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="nc"&gt;.terminal-window&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;softBounce&lt;/span&gt; &lt;span class="m"&gt;1s&lt;/span&gt; &lt;span class="n"&gt;ease-in&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;And we're done! Check out the working version here: &lt;a href="https://mailpace.com" rel="noopener noreferrer"&gt;https://mailpace.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>tutorial</category>
      <category>css</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>The trick to making console.log play nice with complex objects</title>
      <dc:creator>Paul Oms</dc:creator>
      <pubDate>Thu, 21 Oct 2021 20:03:24 +0000</pubDate>
      <link>https://dev.to/ehlo_250/the-trick-to-making-consolelog-play-nice-with-complex-objects-gma</link>
      <guid>https://dev.to/ehlo_250/the-trick-to-making-consolelog-play-nice-with-complex-objects-gma</guid>
      <description>&lt;p&gt;&lt;code&gt;console.log&lt;/code&gt; is useful in a lot of situations (although there are &lt;a href="https://nodejs.org/en/docs/guides/debugging-getting-started/"&gt;usually better ways to debug&lt;/a&gt; if that's what you're doing).&lt;/p&gt;

&lt;p&gt;In the browser &lt;code&gt;console.log&lt;/code&gt; works well with objects, you can drill down as much as you need. But in Node.js, when you look at the output of a nested object, you'll often see this:&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;$&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="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;d&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="nl"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Object&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;d: {}&lt;/code&gt; was replaced with &lt;code&gt;[Object]&lt;/code&gt;. But why?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's because the command line / terminal doesn't have a nice UI for drilling down, so Node attempts to print up to three levels deep. Beyond three levels it just prints &lt;code&gt;[Object]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is controlled by a variable in the node 'util' module, called &lt;code&gt;depth&lt;/code&gt;, which defaults to &lt;code&gt;2&lt;/code&gt;. You can set it yourself here:&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;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;util&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt; &lt;span class="o"&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;// top level only, e.g.:&lt;/span&gt;
&lt;span class="c1"&gt;// { a: [Object] }&lt;/span&gt;
&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;util&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// print everything, e.g.: &lt;/span&gt;
&lt;span class="c1"&gt;// {&lt;/span&gt;
&lt;span class="c1"&gt;//  a: { b: { c: { d: {} } } }&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's not a great idea to change an underlying variable, as it might come back to bite later. So a cleaner way is to convert the JSON object to a &lt;code&gt;string&lt;/code&gt; and log that. We can use node's built in &lt;code&gt;JSON&lt;/code&gt; class and the &lt;code&gt;stringify&lt;/code&gt; method:&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;complexObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;d&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="nx"&gt;log&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="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;complexObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&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="c1"&gt;// {&lt;/span&gt;
&lt;span class="c1"&gt;//   "a": {&lt;/span&gt;
&lt;span class="c1"&gt;//     "b": {&lt;/span&gt;
&lt;span class="c1"&gt;//       "c": {&lt;/span&gt;
&lt;span class="c1"&gt;//         "d": {}&lt;/span&gt;
&lt;span class="c1"&gt;//       }&lt;/span&gt;
&lt;span class="c1"&gt;//     }&lt;/span&gt;
&lt;span class="c1"&gt;//   }&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note that the 3rd parameter of &lt;code&gt;JSON.stringify&lt;/code&gt;, Number &lt;code&gt;2&lt;/code&gt;, controls the number of spaces of indentation, and the 2nd parameter can be used to filter or adjust the objects and properties shown.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now you can really see what's in those deep Objects.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>node</category>
      <category>webdev</category>
    </item>
    <item>
      <title>What happens when you send an email to spam?</title>
      <dc:creator>Paul Oms</dc:creator>
      <pubDate>Wed, 20 Oct 2021 16:07:36 +0000</pubDate>
      <link>https://dev.to/ehlo_250/what-happens-when-you-send-an-email-to-spam-56ak</link>
      <guid>https://dev.to/ehlo_250/what-happens-when-you-send-an-email-to-spam-56ak</guid>
      <description>&lt;p&gt;TDLR: When you hit “Report spam”, this happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The offending email is moved to the spam folder&lt;/li&gt;
&lt;li&gt;Spam detection systems are updated&lt;/li&gt;
&lt;li&gt;If registered, an email complaint is sent through the FBL program in ARF format&lt;/li&gt;
&lt;li&gt;The original sender should take action to prevent further spam&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Read on for more details, the extended, original post is on the OhMySMTP blog here: &lt;a href="https://blog.ohmysmtp.com/blog/what-happens-when-you-send-an-email-to-spam/"&gt;https://blog.ohmysmtp.com/blog/what-happens-when-you-send-an-email-to-spam/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When you hit "Report Spam", at a minimum most email clients will place the email in a spam folder, and many providers will also update their spam detection models to prevent future spam.&lt;/p&gt;

&lt;p&gt;That is helpful, but what about the person who sent the original email? If it genuinely is spam, then who cares? But perhaps the email was mis-categorized, or the sender wasn’t aware that it would be marked as spam. They need to know about spam reports to correct the issue.&lt;/p&gt;

&lt;p&gt;There’s a solution: &lt;strong&gt;Feedback Loops (aka FBLs)&lt;/strong&gt;. FBLs are systems that email inbox providers have set up to send back details of any emails that have been marked as spam to the original sender's platform.&lt;/p&gt;

&lt;p&gt;Email providers and high volume senders can register for the FBL with the email providers, and they will share these spam reports (also known as Complaints) to a Feedback email address. The sender can then review these and choose to stop sending to the addresses or take other action.&lt;/p&gt;

&lt;p&gt;There are FBL programs at the big inbox providers (Google, Microsoft, Yahoo), and &lt;a href="https://validity.com"&gt;https://validity.com&lt;/a&gt; have consolidated many smaller systems. Reports are sent using the &lt;a href="https://en.wikipedia.org/wiki/Abuse_Reporting_Format"&gt;Abuse Reporting Format&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Going through the FBL process can take a long time (hours or days), and many senders/services are not registered in the first place. &lt;/p&gt;

&lt;p&gt;Therefore to reduce spam in your inbox your best bet is to look out for unsubscribe links or use &lt;a href="https://www.idbloc.co/"&gt;temporary email addresses&lt;/a&gt; and turn them off when done.&lt;/p&gt;

</description>
      <category>email</category>
      <category>beginners</category>
      <category>cloud</category>
      <category>inthirtyseconds</category>
    </item>
    <item>
      <title>🤷‍♀️ The easiest way to monitor your app in production is email?</title>
      <dc:creator>Paul Oms</dc:creator>
      <pubDate>Tue, 19 Oct 2021 11:37:16 +0000</pubDate>
      <link>https://dev.to/ehlo_250/the-easiest-way-to-monitor-your-app-in-production-is-email-1c4h</link>
      <guid>https://dev.to/ehlo_250/the-easiest-way-to-monitor-your-app-in-production-is-email-1c4h</guid>
      <description>&lt;p&gt;It's really helpful to have a way to track what's going on with your application in production, things like: number of user sign ups, status of user accounts, number of X new database entries etc. Out of the box dashboards like &lt;a href="https://github.com/sferik/rails_admin"&gt;Rails Admin&lt;/a&gt; are great but only go so far, eventually you will want significant customizations.&lt;/p&gt;

&lt;p&gt;You could spend time coding a beautiful, custom dashboard to show you a summary of everything in real time. On top of coding it you'll spend time designing, testing, securing and deploying it. Plus digging into problems that pop up (what is monitoring your monitoring dashboard?). You might instead plug-in a 3rd party dashboard and query using that, which can be expensive and just as time consuming. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There's an easier way, which doesn't take away time from building your app or product: &lt;strong&gt;email&lt;/strong&gt;&lt;/strong&gt;. Specifically through email daily digests &amp;amp; notifications.&lt;/p&gt;

&lt;p&gt;Compared to building a dashboard the advantages are numerous:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster to code&lt;/li&gt;
&lt;li&gt;Doesn't consume any resources&lt;/li&gt;
&lt;li&gt;Minimal DevOps required&lt;/li&gt;
&lt;li&gt;Very easy to extend / customize&lt;/li&gt;
&lt;li&gt;Reuses your existing tech stack&lt;/li&gt;
&lt;li&gt;Almost no maintenance&lt;/li&gt;
&lt;li&gt;Regular notifications (you won't forget to check the dashboard)&lt;/li&gt;
&lt;li&gt;Everything is timestamped and archived by default&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main disadvantage is that it's not real time/interactive. But if you spot anything that requires a detailed drill down, you'll probably want to go in and do a detailed investigation through your app anyway.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Ultimately you should have several different tools monitoring your app to ensure things stay smooth e.g. external uptime monitors, exception trackers, log analysis, CPU/Memory usage tracking etc. This is an additional one you can add on for business logic monitoring&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  An example in Rails
&lt;/h2&gt;

&lt;p&gt;To show you how easy this is, we'll create an example that sends the latest count of users every day to ourselves at the same time. You could extend this to send updates on anything tracked in your database (e.g. most popular article, unusual user behavior etc.)&lt;/p&gt;

&lt;p&gt;Prerequisites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Rails 6+ app&lt;/li&gt;
&lt;li&gt;A transactional email provider (e.g. Amazon SES, Postmark etc.) I recommend &lt;a href="https://ohmysmtp.com"&gt;OhMySMTP&lt;/a&gt; and the associated Rails Gem: &lt;a href="https://github.com/ohmysmtp/ohmysmtp-rails"&gt;https://github.com/ohmysmtp/ohmysmtp-rails&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Email setup
&lt;/h3&gt;

&lt;p&gt;Add this line to your application's Gemfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'ohmysmtp-rails'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;bundle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configure your API Token
&lt;/h3&gt;

&lt;p&gt;Retrieve your API token for your sending domain from &lt;a href="https://app.ohmysmtp.com"&gt;OhMySMTP&lt;/a&gt;. You can find it under Organization -&amp;gt; Domain -&amp;gt; API Tokens&lt;/p&gt;

&lt;p&gt;Open the credentials editor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails secret
rails credentials:edit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add your token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ohmysmtp_api_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TOKEN_GOES_HERE"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally set OhMySMTP as your mailer in &lt;code&gt;config/application.rb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_mailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delivery_method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:ohmysmtp&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_mailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ohmysmtp_settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;api_token: &lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ohmysmtp_api_token&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setup our email template
&lt;/h3&gt;

&lt;p&gt;We'll prepare an email template to send to ourselves or our team:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails generate mailer monitoring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which will create our mailer and folders to place our email templates in. &lt;/p&gt;

&lt;p&gt;Modify the &lt;code&gt;app/mailers/monitoring_mailer.rb&lt;/code&gt; file to include our daily notification and override our ApplicationMailer defaults:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MonitoringMailer&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationMailer&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;daily_update&lt;/span&gt;
    &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s2"&gt;"your-email-address@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;subject: &lt;/span&gt;&lt;span class="s2"&gt;"User Count Update"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When this is called ActionMailer will look for a &lt;code&gt;daily_update&lt;/code&gt; template to use. So let's go ahead and create that. &lt;/p&gt;

&lt;p&gt;Create a file called &lt;code&gt;daily_update.html.erb&lt;/code&gt; in &lt;code&gt;app/views/monitoring_mailer&lt;/code&gt; with the email contents (note the placeholder for our User Count, &lt;code&gt;count_of_users&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;'text/html; charset=UTF-8'&lt;/span&gt; &lt;span class="na"&gt;http-equiv=&lt;/span&gt;&lt;span class="s"&gt;'Content-Type'&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Total users: &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;count_of_users&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Our email is ready to go, now we need to calculate the &lt;code&gt;count_of_users&lt;/code&gt; variable and trigger the email regularly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a Rake Task
&lt;/h3&gt;

&lt;p&gt;A rake task is basically script that can be called from another location. We'll put our simple monitoring logic and email trigger here.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;TIP: For a more complex example in Rails put your monitoring logic in a Job, and call the job from the Rake task. Jobs can be run asynchronously by Sidekiq or another queue manager and are much more easily tested&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails generate task monitor_my_app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will generate a file &lt;code&gt;lib/tasks/monitor_my_app.rake&lt;/code&gt; - change the contents to the following (and here is where you'll make most customizations to fit your app):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;namespace&lt;/span&gt; &lt;span class="ss"&gt;:monitor_my_app&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;email_latest_stats: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:environment&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
     &lt;span class="c1"&gt;# Count number of new users&lt;/span&gt;
     &lt;span class="vi"&gt;@count_of_users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"created_at &amp;gt; ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ago&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;

     &lt;span class="c1"&gt;## Send the email with latest count&lt;/span&gt;
     &lt;span class="no"&gt;MonitoringMailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;count_of_users: &lt;/span&gt;&lt;span class="vi"&gt;@count_of_users&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;daily_update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deliver_now&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test it by running our Rake task
&lt;/h3&gt;

&lt;p&gt;We can call this locally with:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rake monitor_my_app:email_latest_stats&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Or if you're using Capistrano for deployments you can run this on production:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bundle exec cap production invoke:rake TASK=monitor_my_app:email_latest_stats&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In your development environment an email should pop up in the browser, in production you'll see it arrive in your inbox in a couple of seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Schedule it to run every day
&lt;/h3&gt;

&lt;p&gt;Rake tasks can be called from Cron, so we'll use the &lt;a href="https://github.com/javan/whenever"&gt;Whenever Gem&lt;/a&gt; to automatically add our job to our Crontab during deployment, and keep our schedule in change control. &lt;/p&gt;

&lt;p&gt;After setting up Whenever, add the following to your &lt;code&gt;schedule.rb&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;every&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;days&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;rake&lt;/span&gt; &lt;span class="s2"&gt;"monitor_my_app:email_latest_stats"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it, after you deploy you'll get an email every day with the total number of users in your app. You can keep extending this with more concepts and daily digest emails - much much easier than building a custom dashboard!&lt;/p&gt;

</description>
      <category>email</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>ruby</category>
    </item>
    <item>
      <title>How to add syntax highlighting to Code Snippets on your website, app or blog</title>
      <dc:creator>Paul Oms</dc:creator>
      <pubDate>Wed, 13 Oct 2021 08:14:32 +0000</pubDate>
      <link>https://dev.to/ehlo_250/how-to-add-syntax-highlighting-to-code-snippets-on-your-website-app-or-blog-2mi2</link>
      <guid>https://dev.to/ehlo_250/how-to-add-syntax-highlighting-to-code-snippets-on-your-website-app-or-blog-2mi2</guid>
      <description>&lt;p&gt;Have you ever wondered how sites like &lt;a href="https://dev.to/"&gt;Dev.to&lt;/a&gt;, programming blogs and landing pages (like &lt;a href="https://ohmysmtp.com"&gt;https://ohmysmtp.com&lt;/a&gt;) show little snippets of code with nice syntax highlighting? Read on to find out how to set this up on your site.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note that there are code screenshot sites like &lt;a href="https://carbon.now.sh/"&gt;https://carbon.now.sh/&lt;/a&gt; that will give you great little screenshots of your code. But these are mere .pngs, they're not accessible, searchable or copy &amp;amp; pasteable. That's NOT what we're going to do here, our output will be fully accessible and appear as text on the page.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Code syntax highlighting looks like magic, but the actual reality is less complex than you might think. Let's get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependencies
&lt;/h2&gt;

&lt;p&gt;We're going to use &lt;a href="https://prismjs.com/"&gt;PrismJS&lt;/a&gt; to do all the heavy lifting. &lt;/p&gt;

&lt;p&gt;There are a couple of choices to bring it into your project. If you're already using a JS Bundler (e.g. esbuild or Webpack) you can setup the &lt;a href="https://github.com/mAAdhaTTah/babel-plugin-prismjs"&gt;Babel Plugin for Prism&lt;/a&gt;. For our demonstration we'll do this the old, simple way, writing our HTML tags directly. &lt;/p&gt;

&lt;p&gt;Start by heading over to &lt;a href="https://prismjs.com/download.html"&gt;https://prismjs.com/download.html&lt;/a&gt; and:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose languages you want to support (keep this as small as possible to reduce file size)&lt;/li&gt;
&lt;li&gt;Pick a theme&lt;/li&gt;
&lt;li&gt;Download the &lt;code&gt;prism.js&lt;/code&gt; and &lt;code&gt;prism.css&lt;/code&gt; files and place them in your project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally we include them in the HTML page. CSS goes in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section and the JS can go at the end of the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tag. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tip: Place the JS at the end of the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; section to avoid delaying loading the rest of the page&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&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;head&amp;gt;&lt;/span&gt;
    ...
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"prism.css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    ...
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"prism.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding the Code snippets
&lt;/h2&gt;

&lt;p&gt;Next we'll add the code snippets we want to highlight plain old &lt;code&gt;&amp;lt;code&amp;gt;&lt;/code&gt; tags. For example:&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;code&amp;gt;&lt;/span&gt;
   const variable = "Here's some JavaScript";
&lt;span class="nt"&gt;&amp;lt;/code&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Refresh the page, and you'll just see the code unhighlighted, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const variable = "Here's some JavaScript";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to tell Prism to highlight the code block and which language to use. To do this we'll add a HTML class to the &lt;code&gt;code&lt;/code&gt; block, in this format: &lt;code&gt;class="language-XXXXX"&lt;/code&gt; where XXXXX is the language, like this:&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;code&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"language-javascript"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
   const variable = "Here's some JavaScript";
&lt;span class="nt"&gt;&amp;lt;/code&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally we'll wrap everything in a &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; tag. This will "Preserve" the formatting and indentation of our code block so we can display it exactly as it would appear in the editor.&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;pre&amp;gt;&amp;lt;code&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"language-javascript"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;const variable = "Here's some JavaScript";&lt;span class="nt"&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that I've removed the line breaks / whitespace (these are usually ignored by HTML parsers). This is a bit awkward to read in our editor, but it means the JavaScript will appear exactly as we want it when rendered by Prism:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Here's some JavaScript&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;h2&gt;
  
  
  How this works
&lt;/h2&gt;

&lt;p&gt;Our original HTML consists of standard &lt;code&gt;&amp;lt;code&amp;gt;&lt;/code&gt; blocks, which are generally used for code examples, so the browser kind of knows how to display these without highlighting.&lt;/p&gt;

&lt;p&gt;The Prism JavaScript scans the page searching for code blocks with "language-XXXXX" tags, and when it finds them it treats these blocks as code that requires syntax highlighting.&lt;/p&gt;

&lt;p&gt;Prism them applies the syntax highlighting by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Breaking each known code string into individual language "tokens"&lt;/li&gt;
&lt;li&gt;Wrapping them in &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; tags &lt;/li&gt;
&lt;li&gt;Categorizing them into types of language token (e.g. operator, punctuation, string etc.) and labelling them &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does most of the work using Regular Expressions, which you can easily read through in the source (e.g. for JavaSript the rules are here: &lt;a href="https://github.com/PrismJS/prism/blob/master/components/prism-javascript.js"&gt;https://github.com/PrismJS/prism/blob/master/components/prism-javascript.js&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;prism.css&lt;/code&gt; rules then take care of the actual colouring. These RegExes and the implementation can be quite complex, but the concept is relatively simple.&lt;/p&gt;

&lt;p&gt;That's it - easy to add, when you know how.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>javascript</category>
      <category>css</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Verify Webhooks in Ruby on Rails with Public Private Key Crytography</title>
      <dc:creator>Paul Oms</dc:creator>
      <pubDate>Tue, 05 Oct 2021 08:28:45 +0000</pubDate>
      <link>https://dev.to/ehlo_250/verify-webhooks-in-ruby-on-rails-with-public-private-key-crytography-4an4</link>
      <guid>https://dev.to/ehlo_250/verify-webhooks-in-ruby-on-rails-with-public-private-key-crytography-4an4</guid>
      <description>&lt;p&gt;When we first launched &lt;a href="https://ohmysmtp.com"&gt;OhMySMTP&lt;/a&gt; we chose &lt;a href="https://paddle.com"&gt;Paddle&lt;/a&gt; as our Payments Provider, primarily because they handle all sales taxes and payment infrastructure globally. One of the things that took longer than it should have was ensuring that alerts (webhooks) received from paddle.com actually come from Paddle. &lt;/p&gt;

&lt;p&gt;Luckily Paddle signs every request using &lt;a href="https://en.wikipedia.org/wiki/Public-key_cryptography"&gt;Public Key Cryptography&lt;/a&gt;, and it works in a similar way to &lt;a href="https://blog.ohmysmtp.com/blog/whats-a-DKIM-record/"&gt;DKIM&lt;/a&gt;. Paddle creates a short signature, using a Private Key specific to our Paddle account, and includes it with every webhook sent from their system, which we can verify on our end using the Public Key (see &lt;a href="https://developer.paddle.com/webhook-reference/verifying-webhooks"&gt;https://developer.paddle.com/webhook-reference/verifying-webhooks&lt;/a&gt; for more details). &lt;strong&gt;Without this a nefarious actor might figure out your webhook endpoint and create a bunch of fake subscriptions/updates in your app.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This verification is great, and Paddle has ok docs on how to do it. But I couldn't get the &lt;a href="https://developer.paddle.com/webhook-reference/verifying-webhooks"&gt;paddle code examples&lt;/a&gt; working in the OhMySMTP Rails app without some frustrating trial and error, so here's an example of how you can implement Paddle webhook endpoints with verification in Ruby on Rails. Broadly this should apply to any language as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependencies
&lt;/h2&gt;

&lt;p&gt;You'll need to ensure you have the following dependencies available in your &lt;code&gt;Gemfile&lt;/code&gt; (OpenSSL and Base64 should already be in Rails):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;php-serialize&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;openssl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;base64&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Code!
&lt;/h2&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;&lt;code&gt;app/controllers/api/paddle_controller.rb&lt;/code&gt;&lt;/strong&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# A standard Rails API endpoint definition&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Api::PaddleController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActionController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;API&lt;/span&gt;
  &lt;span class="c1"&gt;# Ensure every request is validated, except when testing&lt;/span&gt;
  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:verify_webhook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;unless: &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"RAILS_ENV"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Select the right method depending on the webhook sent by paddle, see full list here https://developer.paddle.com/webhook-reference/&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;paddle&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"alert_name"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s2"&gt;"subscription_created"&lt;/span&gt;
      &lt;span class="n"&gt;subscription_created&lt;/span&gt;
    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="s2"&gt;"subscription_payment_succeeded"&lt;/span&gt;
      &lt;span class="n"&gt;subscription_payment_success&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;error: &lt;/span&gt;&lt;span class="s2"&gt;"alert_name &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'alert_name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; does not match a known webhook / alert"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="ss"&gt;status: :not_found&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subscription_created&lt;/span&gt;
    &lt;span class="c1"&gt;# Application logic here (e.g. update user account)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subscription_payment_success&lt;/span&gt;
    &lt;span class="c1"&gt;# Application logic here (e.g. update user account)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="c1"&gt;# The actual verification takes place below&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;verify_webhook&lt;/span&gt;
    &lt;span class="c1"&gt;# Copy and paste from https://vendors.paddle.com/public-key&lt;/span&gt;
    &lt;span class="c1"&gt;# You should store this in an environment variable in a real app, and note the line breaks / formatting which must match exactly&lt;/span&gt;
    &lt;span class="n"&gt;public_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-----BEGIN PUBLIC KEY-----
-----END PUBLIC KEY-----"&lt;/span&gt;

    &lt;span class="c1"&gt;# We take all the params available as JSON structure&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;accept_all_params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_json&lt;/span&gt;

    &lt;span class="c1"&gt;# Extract the signature itself to verify later&lt;/span&gt;
    &lt;span class="n"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"p_signature"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="c1"&gt;# Remove the unsigned params (the signature itself and additional params from Rails)&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"p_signature"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"controller"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Sort &amp;amp; serialize params to match the original way Paddle signs the request&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;params_sorted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_by&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_value&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;params_serialized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;PHP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params_sorted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Verify the params and respond with 403 if verification fails&lt;/span&gt;
    &lt;span class="n"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OpenSSL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Digest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SHA1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pub_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OpenSSL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PKey&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;RSA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;public_key&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;public_key&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;pub_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params_serialized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;accept_all_params&lt;/span&gt;
    &lt;span class="c1"&gt;# We do this because paddle has a p_signature, and if they add extra params in the future&lt;/span&gt;
    &lt;span class="c1"&gt;# we need to ensure the signature still validates&lt;/span&gt;

    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;permit!&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;That's it - easy when you know how.&lt;/p&gt;

</description>
      <category>cryptography</category>
      <category>webhooks</category>
      <category>rails</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Free (MIT) Transactional HTML Email templates built using Tailwind CSS</title>
      <dc:creator>Paul Oms</dc:creator>
      <pubDate>Tue, 28 Sep 2021 07:59:00 +0000</pubDate>
      <link>https://dev.to/ehlo_250/set-of-free-mit-transactional-email-templates-built-using-tailwind-css-4n12</link>
      <guid>https://dev.to/ehlo_250/set-of-free-mit-transactional-email-templates-built-using-tailwind-css-4n12</guid>
      <description>&lt;p&gt;Today I'm releasing a set of &lt;a href="https://github.com/ohmysmtp/templates" rel="noopener noreferrer"&gt;free HTML Transactional Email Templates&lt;/a&gt;. Check them out here: &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/mailpace" rel="noopener noreferrer"&gt;
        mailpace
      &lt;/a&gt; / &lt;a href="https://github.com/mailpace/templates" rel="noopener noreferrer"&gt;
        templates
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A set of gorgeous Transactional HTML Email Templates built on TailwindCSS
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;These templates are super cool because they're built using &lt;a href="https://tailwindcss.com" rel="noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt;, and the repository contains all of the build tools required to use Tailwind in HTML emails, making sure they render correctly across different browsers &amp;amp; devices. This is all powered by &lt;a href="https://maizzle.com" rel="noopener noreferrer"&gt;Maizzle&lt;/a&gt; - which provides CSS post processing to embed Tailwind into an email and apply a bunch of HTML email best practices.&lt;/p&gt;

&lt;p&gt;Oh and they support &lt;code&gt;Dark Mode&lt;/code&gt; out of the box ;-)&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Tailwind CSS?
&lt;/h2&gt;

&lt;p&gt;Tailwind CSS gives you "utility classes" to use in your HTML such as &lt;code&gt;p-48&lt;/code&gt; (apply padding of 48px to all 4 sides of an element) or &lt;code&gt;rotate-90&lt;/code&gt; (rotate an element 90 degrees). You can use these utility classes to build almost anything in HTML, without touching CSS.&lt;/p&gt;

&lt;p&gt;At first this seems a bit awkward, like you're inlining CSS inside your HTML, but after you get used to the syntax it makes adding styling to HTML pages extremely productive. You don't need to switch context to CSS and come up with class names or an informal class hierarchy. It's hard to state how much more productive you can be in Tailwind, but &lt;strong&gt;it really is a massive boost when you're building HTML apps&lt;/strong&gt; (or  email templates in this case!)&lt;/p&gt;

&lt;p&gt;If you do want to make components (e.g. &lt;code&gt;.btn&lt;/code&gt;) you can use the &lt;code&gt;@apply&lt;/code&gt; CSS directive to bundle up styles into components and reuse them.&lt;/p&gt;

&lt;h2&gt;
  
  
  But how?
&lt;/h2&gt;

&lt;p&gt;The included &lt;a href="https://maizzle.com" rel="noopener noreferrer"&gt;Maizzle&lt;/a&gt; configuration post-processes all of your HTML using various &lt;a href="https://postcss.org/" rel="noopener noreferrer"&gt;PostCSS&lt;/a&gt; plugins. In the end, you just write normal HTML with Tailwind and Maizzle takes care of building your HTML templates out, and spits them into the &lt;code&gt;/dist&lt;/code&gt; directory when complete.&lt;/p&gt;

&lt;p&gt;Easy!&lt;/p&gt;

&lt;h2&gt;
  
  
  Who can use them?
&lt;/h2&gt;

&lt;p&gt;Anyone, for any reason. They're MIT licensed so feel free to do what you like with them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are Transactional Emails?
&lt;/h2&gt;

&lt;p&gt;Transactional emails are automated emails sent on a one-time basis, typically triggered by a user interaction or system update in your website / app. They are usually sent to one person at a time, and contain important or actionable information for your  service.&lt;/p&gt;

&lt;p&gt;Typical transactional email types include account notifications, receipts, updates, password reset notifications, magic sign up links and many, many more. &lt;/p&gt;

&lt;h2&gt;
  
  
  Want to send your transactional emails through an Independent 💪, privacy-focused 🕵️ and eco-friendly 🌱 provider?
&lt;/h2&gt;

&lt;p&gt;Sign up for an account at &lt;a href="https://ohmysmtp.com" rel="noopener noreferrer"&gt;OhMySMTP&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tailwindcss</category>
      <category>html</category>
      <category>email</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
