<?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>Forem: Alex Jacobs</title>
    <description>The latest articles on Forem by Alex Jacobs (@lexjacobs).</description>
    <link>https://forem.com/lexjacobs</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%2F147187%2F3c323212-91b4-46de-b3ea-4dd9d39525a5.jpeg</url>
      <title>Forem: Alex Jacobs</title>
      <link>https://forem.com/lexjacobs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/lexjacobs"/>
    <language>en</language>
    <item>
      <title>How to set up a mechanical keyboard with super light-touch switches.</title>
      <dc:creator>Alex Jacobs</dc:creator>
      <pubDate>Tue, 18 Feb 2020 01:33:30 +0000</pubDate>
      <link>https://forem.com/lexjacobs/how-to-set-up-a-mechanical-keyboard-with-super-light-touch-switches-5bfn</link>
      <guid>https://forem.com/lexjacobs/how-to-set-up-a-mechanical-keyboard-with-super-light-touch-switches-5bfn</guid>
      <description>&lt;h3&gt;
  
  
  How to set up a mechanical keyboard with super light-touch switches.
&lt;/h3&gt;

&lt;h4&gt;
  
  
  My journey to set up the easiest touch and shortest travel possible on a keyboard led me to a Planck-EZ keyboard with switches with modified springs, but it won’t stop there.
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;This article is aimed primarily at people with unusual keyboarding needs, such as those dealing with RSI (repetitive stress&lt;/strong&gt;  &lt;strong&gt;injury&lt;/strong&gt; ). There is a difficult learning curve to using super light touch switches, during which time you will likely see a lot of extra home row characters sprinkled throughout your documents, as you rest your hands on the keyboard and unintentionally actuate the keys. j&lt;/p&gt;

&lt;h3&gt;
  
  
  Background
&lt;/h3&gt;

&lt;p&gt;My life as a keyboard-using person temporarily ended for a few years, starting in early 2016, when the cumulative effects of RSI caused typing to be too painful, and my arms too achy to comfortably carry out some basic household tasks, or even continue playing much guitar.&lt;/p&gt;

&lt;p&gt;In order to continue working in my field as a software engineer, I then became a voice coder, using VoiceCode (extinct) and then Talon, and got good enough with the software to be able to work professionally, largely hands free.&lt;/p&gt;

&lt;p&gt;Eventually due to my professional role switching to teaching and mentoring, my needs for keyboarding increased. I had healed quite a bit from my injuries, but knew that full time keyboarding would likely lead me down the same path of eventual physical injury.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Daydreaming about the possibility of being able to use a keyboard again, my research was inspired by the way I’ve always set up my guitars, with the lowest “action” (string height off of the neck) possible, leading to the least finger strain.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That search led me to a slack posting by a member of the Talon community who mentioned they had made a super light-touch keyboard by cutting the springs in their Cherry MX reds to bring the actuation force down.&lt;/p&gt;

&lt;p&gt;My research that I’ve documented in this post included buying a small wire cutter and trying some experiments with spring cutting, but I found it lacking in precision, enough to want to try something else. It turns out that there are now precision springs with a huge variety of actuation forces easily available online. You can view a list at &lt;a href="https://www.spritdesigns.com/mx"&gt;https://www.spritdesigns.com/mx&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another piece of the puzzle was researching key switches that would work well with reduced spring forces. I found a variety of low-actuation distance “speed” switches that triggered a key press around 1.1mm. For comparison, a normal Cherry MX red actuates around 2mm. If you have a ruler with millimeters on it and actually look at the difference here, it would seem to be ridiculously negligible. I thought the same. &lt;em&gt;But&lt;/em&gt;… here is an important distinction that made a big difference in terms of my personal need. Not only did I want keys that actuated by basically breathing on them, I also wanted to lower the amount of total travel that was possible during a keypress, so that even if I had the hand strength to type relatively hard on a particular day, the design of the key would limit the amount of finger movement I was doing.&lt;/p&gt;

&lt;p&gt;After using a bunch of super-light modified Gateron Clear switches, I found that their greater overall travel of 4.0mm was irritating my RSI. The Kailh Speed Silvers have an overall travel of 3.5mm which by itself is a ridiculously small difference, but I further lower the key travel with 2 stacked O-rings on the key cap (details below). Over hundreds or thousands of key presses over the day, these modifications make a big difference for my RSI.&lt;/p&gt;

&lt;p&gt;The final piece of the puzzle was locating a keyboard that would allow me to experiment with different key/spring combinations. I landed on the Planck-EZ which seemed too good to be true. It’s a Planck keyboard with the ability to hot-swap your key switches. I ordered one with a set of Kailh Speed Silver switches installed. I also bought some different switches to experiment with: Kailh copper switches, and some Gateron clears.&lt;/p&gt;

&lt;p&gt;The Kailh copper switches were immediately out for me. The tactile bump was totally counter to my goal of lightest touch. They were irritating to my RSI, and with the lighter springs I was using, didn’t fully rebound after pressing. They helped me realize that I should only focus my further research efforts on linear switches.&lt;/p&gt;

&lt;p&gt;The Kailh Speed Silver switches have an actuation point around 1.1mm. They were the switches that I ordered with the board, and I swapped the springs with 35g Spirit MX springs. They actuate at 25g. The 30g Spirit MX springs I also bought were not springy enough to fully ‘reset’ the Kailh Speed Silver. If I were to lube the switch, it might be sufficient, but I have not tried that yet.&lt;/p&gt;

&lt;p&gt;You can follow this link to &lt;a href="https://configure.ergodox-ez.com/planck-ez/layouts/qm7oK/latest/0"&gt;my layout on the Ergodox EZ Configurator site&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is how I have arranged the modified switches on my Planck-ez&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TMvT8d-i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A4xB8z3jrQdYHKat-JOio4Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TMvT8d-i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A4xB8z3jrQdYHKat-JOio4Q.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the key for the colors above:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SEQSOYIm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/848/1%2AErsi6jNR0qa6A_z7EiEwsA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SEQSOYIm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/848/1%2AErsi6jNR0qa6A_z7EiEwsA.png" alt="Speed Silver switches with 35g springs. Gateron Clear with 30g springs. Gateron clear with original springs."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Gateron clear springs are sold with 35g springs installed. However, that is the actuation force, which means that the finger force curve goes up from there. The Spirit MX compatible spring that I swapped into the Gateron Clears are sold as 30g, but they have a 20g actuation force. The 30g Spirit springs were insufficient to reset the Kailh Speed Silvers, but work very well for the Gateron Clears, making them incredibly light to actuate.&lt;/p&gt;

&lt;p&gt;The modified Gateron Clears, with their slightly longer actuation point serve as a very useful “guard row” against accidental key presses that come from just moving your hands anywhere near the board. You may not be aware of how much pressure you put on your keys while you are subconsciously locating the right key to press. When you remove virtually all resistance from the keys with the modified springs, and lower the actuation point (on the letter keys) to 1.1mm with a Speed series switch, you will learn just how sloppy most of us are when it comes to locating keys.&lt;/p&gt;

&lt;h4&gt;
  
  
  Swapping Springs
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;The Gateron Clears are easy to open up with an MX Switch Top Removal Tool.&lt;/li&gt;
&lt;li&gt;The Kailh switches require some semi-dangerous knife work to open up. &lt;a href="https://deskthority.net/viewtopic.php?p=221497&amp;amp;sid=822191ede5d71e9fa4a12a1904f0765c#p221497"&gt;Here is a good set of photos showing the process&lt;/a&gt;. Instead of a large blade, I use a thin box cutter blade similar to the one shown below. Either way, you are at a good risk of blood letting.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  O-rings
&lt;/h4&gt;

&lt;p&gt;I was able to further reduce the total key travel distance an estimated 1 or 2 mm with O rings. They also make the keyboard much quieter. They also raise the height of the keys off the board by a couple millimeters. I bought a set. They sit on top of each other nicely. I put the large ring on first, push it down with an unscrewed ball point pen body, and then put the small one on top. I make sure not to push down too hard on the key caps when attaching them to the stem. Yes, this means that they keycaps fall off occasionally if I carelessly drag something across the edge of a key when moving things on my desk.&lt;/p&gt;

&lt;p&gt;The modified Gateron Clears with O-rings actuate much closer to the end of their total key travel than the modified Kailh Speed Silvers. I can actuate the Speed Silver with a super light touch and sometimes not even bottom out. The Gaterons are almost bottomed out (with O-rings) when they actuate.&lt;/p&gt;

&lt;p&gt;Here are the sizes of O-rings that I am using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Large: OD 9 x 2.5mm&lt;/li&gt;
&lt;li&gt;Small: OD 8 x 1.5mm&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QjoR1BXk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2Ao6nblZvyj3C7zfcVaGk9Wg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QjoR1BXk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2Ao6nblZvyj3C7zfcVaGk9Wg.jpeg" alt=""&gt;&lt;/a&gt;Larger O-ring on the bottom, smaller one layered right on top.&lt;/p&gt;

&lt;p&gt;You can even see in the following photo, on the spacebar, which only has 1 large O-ring, that it sits lower than the other keys, which all have 2 O-rings. The O-rings significantly affect key height, overall travel, and sound. I tried removing them recently and found I’ve really gotten used to the feel. I found the travel and noise irritating. Many mechanical keyboard fans love the noise and feel of their keys, without O-rings. I probably would as well, but due to RSI, my goals unfortunately have to focus on ergonomic purpose over aesthetics.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Lj8SBa_c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AlVVA-XYiMGBWHlyi8IyCIg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Lj8SBa_c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AlVVA-XYiMGBWHlyi8IyCIg.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the type of box knife I use to open the Kailh Speed Silver switches.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Iqxy-qDF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/750/0%2A2Udwoi42rOexePbw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Iqxy-qDF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/750/0%2A2Udwoi42rOexePbw.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is my board with all the keycaps removed and placed on the table above the board and exposed switches. Red sharpie dots were added to the modified switches to be able to identify the ones with the swapped springs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0GKoSJHL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2ALI30aWVxUXwL7vzXbBEbiw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0GKoSJHL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2ALI30aWVxUXwL7vzXbBEbiw.jpeg" alt="Kailh Speed Silver and Gateron Clears on the same board. Red sharpie marker dots on the key switches with modified springs."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My weirdo layout was inspired by my TypeMatrix 2030, as an attempt to make more space for my wrists. It’s still not enough for me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--v0LOJ0-9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A0K3LT073ZPz6a_dpKDmQvA.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--v0LOJ0-9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A0K3LT073ZPz6a_dpKDmQvA.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DHk8nAh---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2Ao-jiPS585FzprclDFzEkUw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DHk8nAh---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2Ao-jiPS585FzprclDFzEkUw.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The following photos show the customized LED settings that correspond to the various programmable “modes”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DBQcES41--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AylJZgoodLPV-CL06Qghqsg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DBQcES41--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AylJZgoodLPV-CL06Qghqsg.jpeg" alt=""&gt;&lt;/a&gt;The “move” layer&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hf3Bn8sh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A6Z2uchKEhv8uJi8dvdnR3g.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hf3Bn8sh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A6Z2uchKEhv8uJi8dvdnR3g.jpeg" alt=""&gt;&lt;/a&gt;The “adjust” layer&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--B8ZdzHf_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AhN2zi-I9vTsJnR6GQu9Q4Q.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--B8ZdzHf_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AhN2zi-I9vTsJnR6GQu9Q4Q.jpeg" alt=""&gt;&lt;/a&gt;The “raise” layer&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RaaMFmvE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AhhWYjDc9meUxtPYqF5g60w.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RaaMFmvE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AhhWYjDc9meUxtPYqF5g60w.jpeg" alt=""&gt;&lt;/a&gt;The “lower” layer&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PAeayKIY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AKR7wi8MVxOrRC5ytCKxGsQ.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PAeayKIY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AKR7wi8MVxOrRC5ytCKxGsQ.jpeg" alt=""&gt;&lt;/a&gt;The “num” layer&lt;/p&gt;

&lt;h4&gt;
  
  
  Where I plan to go from here
&lt;/h4&gt;

&lt;p&gt;So it turns out through researching for this post, you can go even lighter. Check out the gboards link below in Further Reading. They’re using low profile Kailh Choc switches with 12g (!!) switches.&lt;/p&gt;

&lt;p&gt;I don’t think I’m ready yet to move further down the rabbit hole to even less keys than I’ve gotten skilled with using on the Planck. The Planck-EZ is an incredible keyboard, and has been my gateway into the world of mechanical keyboards. My persistent problem with it is that, similar to any non-split keyboard, my hands are stuck too close together, which is causing wrist pain for me. Even with my weirdo layout that puts an extra row of keys between the left and right hand keys, the keys feel too close together. I also compared the Planck-EZ to my mac laptop keyboard, and the Planck-EZ offers more width than the laptop keyboard. It basically seems that any solid keyboard is going to bother my RSI.&lt;/p&gt;

&lt;p&gt;So, next, I want to purchase an ErgoDox-EZ, which is a split keyboard that will allow me to space the right and left halves as far apart as I need to to remove the wrist bend that is bothering me so much. And, it will of course allow me to make the same switch mods that I was so easily able to add to the Planck-EZ. My plans for setting up the ErgoDox-EZ are to minimize use of the thumb clusters. I imagine this will change, but I initially only plan to use the most easily accessible thumb buttons for a space bar, and continue to use the ErgoDox as if it is a Planck. The beauty of learning to type and code efficiently with the Planck is that I’ve found it really great to be able to use the programmability of the firmware to “move the keys to my fingers rather than the other way around”. I will initially try to keep using my super compact layout that I’ve mastered with the Planck, but with the ergonomic benefit of the ErgoDox split keyboard. I’ll likely purchase the tent kit as well to experiment with different tilting angles to see if I can provide even greater ease to my fingers.&lt;/p&gt;

&lt;p&gt;You can &lt;a href="https://configure.ergodox-ez.com/ergodox-ez/layouts/64wpE/latest/0"&gt;check out my ErgoDox-EZ layout here&lt;/a&gt;. At the present moment, I’ve programmed it without even having the board, so I’m sure it will change a lot after actually having the board.&lt;/p&gt;

&lt;h4&gt;
  
  
  Further Reading
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Full list of Spirit Designs MX compatible springs &lt;a href="https://www.spritdesigns.com/mx"&gt;https://www.spritdesigns.com/mx&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Next-level super light touch chorded keyboards (check out the blog) &lt;a href="https://www.gboards.ca/"&gt;https://www.gboards.ca/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A story about choosing an ErgoDox-EZ &lt;a href="https://fangpenlin.com/posts/2019/09/18/ninja-speed-vim-like-debugging-with-ergonomic-keyboard-and-trackpad/"&gt;https://fangpenlin.com/posts/2019/09/18/ninja-speed-vim-like-debugging-with-ergonomic-keyboard-and-trackpad/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A detailed story about &lt;em&gt;not&lt;/em&gt; choosing an ErgoDox-EZ and going with a Gergo instead (great links within) &lt;a href="https://curiosityoverflow.xyz/posts/ergonomic-superiority/"&gt;https://curiosityoverflow.xyz/posts/ergonomic-superiority/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Acknowledgements and disclaimer
&lt;/h4&gt;

&lt;p&gt;I’m grateful to the hundreds of keyboard enthusiasts that have shared their knowledge on online forums, and the manufacturers that created the parts that went together to make my board. Also, this is description of how to make the lightest touch and shortest travel keyboard that works for my personal needs. The world of mechanical keyboards is very wide, and there may be ways to go beyond what I’ve found in terms of shorter travel, or lighter keys. One method is to use a “chorded” keyboard. And finally, nothing in this article should be taken as medical advice. If you have, or think you have RSI, please seek professional medical help. It’s a serious condition that can take a frustrating amount of time and resources to figure out how to manage and / or heal.&lt;/p&gt;

</description>
      <category>keyboard</category>
      <category>mechanicalkeyboards</category>
      <category>ergodox</category>
      <category>ergonomics</category>
    </item>
    <item>
      <title>The Infinite Coastline Paradox and the importance of working through technical challenges on your own</title>
      <dc:creator>Alex Jacobs</dc:creator>
      <pubDate>Fri, 11 Oct 2019 20:04:00 +0000</pubDate>
      <link>https://forem.com/lexjacobs/the-infinite-coastline-paradox-and-the-importance-of-working-through-technical-challenges-on-your-own-4k18</link>
      <guid>https://forem.com/lexjacobs/the-infinite-coastline-paradox-and-the-importance-of-working-through-technical-challenges-on-your-own-4k18</guid>
      <description>&lt;h3&gt;
  
  
  The Infinite Coastline Paradox and the importance of working through technical challenges on your own
&lt;/h3&gt;

&lt;h4&gt;
  
  
  There are unknown worlds to explore, hidden within the seemingly simplest of tasks
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7Vmb0u5E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AHskC5pRuzg2YK4FdsjdPNg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7Vmb0u5E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AHskC5pRuzg2YK4FdsjdPNg.jpeg" alt="coastline"&gt;&lt;/a&gt;&lt;a href="https://www.pexels.com/photo/aerial-view-beach-beautiful-cliff-462162/"&gt;&lt;/a&gt;&lt;a href="https://www.pexels.com/photo/aerial-view-beach-beautiful-cliff-462162/"&gt;https://www.pexels.com/photo/aerial-view-beach-beautiful-cliff-462162/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;According to the &lt;a href="https://en.wikipedia.org/wiki/Coastline_paradox"&gt;Infinite Coastline Paradox&lt;/a&gt;, the length of the coastline increases as our measurements become more precise. Imagine measuring the length of the coastline around Great Britain. If you only had a meter stick to lay end to end around the entire coast, you would get a seemingly accurate measurement. Now imagine you were to re-measure the coastline with a tiny centimeter stick. Now you can fit your measuring device into many more nooks and crevices around the edge of the landmass, which would increase the length of your total measurement considerably. As you can imagine how this continues, the smaller your measuring device, the more accurately you can fit it into smaller and smaller details of the coastline. Which means the length of the coastline is theoretically infinite, as your measuring device becomes more and more accurate.&lt;/p&gt;

&lt;p&gt;Now let’s apply this concept to the idea of solving technical challenges. Perhaps the best way to transfer to concept over to discrete challenges is in the context of “toy problems” which are the “short stories of coding challenges”. These are discrete algorithmic tasks that are generally meant to be solved in a single session, and are often used as gating assessments during technical interviews.&lt;/p&gt;

&lt;p&gt;Most published toy problems also have published solutions and video lectures on how to solve them. A common pitfall would be to turn to a video or solution &lt;strong&gt;before spending time trying to solve it on your own.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When first looking at and estimating the steps required to solve a technical challenge, you might compare that to looking at a satellite or aerial photo of a large land mass. The relative size and area of the structure are apparent, and a rough estimate can be made as to what sort of effort it would take to circumnavigate the “landmass of the problem”.&lt;/p&gt;

&lt;p&gt;If you then take the shortcut of looking at the solution, you might be able to memorize the steps and repeat a solution to the problem at hand. But that is kind of like measuring the coastline of the landmass with a large stick. So what was lost by not working through the problem on your own? Well, when you really get inside a problem that you thought required only 5 steps to solve, that’s when you start to look at it more closely, like bringing out a more accurate measurement device. At that point, you might find that the 5 steps each had 5 steps of their own hidden inside. And what if each of those 5 steps had multiple additional steps?&lt;/p&gt;

&lt;p&gt;To work through all those steps on your own, you get to explore inside hidden layers of complexity and explore the potentially vast surface area, hiding treasures of discovery and learning. All of those hidden layers that you go through on your own will also inform your problem solving process going forward, informing all of the future challenges you might encounter as well.&lt;/p&gt;

&lt;p&gt;This blog post is a transcription of a motivational talk I gave to one of the groups of students I currently work with as a full time Technical Mentor at &lt;a href="https://www.hackreactor.com/onsite-immersive"&gt;Hack Reactor&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascripttips</category>
      <category>learningtocode</category>
      <category>javascript</category>
      <category>interviewprep</category>
    </item>
    <item>
      <title>Installing Sendy as a replacement for Mailchimp — 2019 version 4 update, the complete guide, with…</title>
      <dc:creator>Alex Jacobs</dc:creator>
      <pubDate>Fri, 03 May 2019 20:40:09 +0000</pubDate>
      <link>https://forem.com/lexjacobs/installing-sendy-as-a-replacement-for-mailchimp-2019-version-4-update-the-complete-guide-with-49oc</link>
      <guid>https://forem.com/lexjacobs/installing-sendy-as-a-replacement-for-mailchimp-2019-version-4-update-the-complete-guide-with-49oc</guid>
      <description>&lt;h3&gt;
  
  
  Installing Sendy as a replacement for Mailchimp — 2019 version 4 update, the complete guide, with automated backups to S3, and https configuration
&lt;/h3&gt;

&lt;h4&gt;
  
  
  This information is compiled from many sources and is the complete record of my successful install. Start here to save yourself a lot of time.
&lt;/h4&gt;

&lt;h3&gt;
  
  
  This install guide you are now reading is for Sendy version 4. It was tested on Sendy version 4.0.2 with the latest server config available on Digital Ocean
&lt;/h3&gt;

&lt;p&gt;The version 3 install guide &lt;a href="https://dev.to/lexjacobs/installing-sendy-as-a-replacement-for-mailchimp2019-update-the-complete-guide-with-automated-3359"&gt;is outlined in my previous article&lt;/a&gt;. It is nearly identical to this install guide, except it was tested on version 3.1.2.3&lt;/p&gt;

&lt;h3&gt;
  
  
  Intro and motivation for the change
&lt;/h3&gt;

&lt;p&gt;I had a new-music-release email list hosted on Mailchimp to send infrequent updates. I learned the hard way that if a basic account is unused for two years, it’s deleted and the old username is permanently blocked. Just vaporized. No notification sent before the deletion, and no easy way to get the unsubscribe data back either. According to Mailchimp’s compliance department, this is a “security measure”. I still had a backup of the original subscriber emails, but not the important records of who previously unsubscribed from my list over the years. So while I could easily reimport my list again with Mailchimp or another host, there is a “spammer blacklist” risk related to sending email to someone who had unsubscribed in the past.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/863/1*knqBax8wtKF8vVcupLhqGg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/863/1*knqBax8wtKF8vVcupLhqGg.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After jumping through hoops with customer service, I was able to get an export of all my previous Mailchimp data, but I was convinced that it makes more sense to find an alternative to the industry heavies such as: Mailchimp, Sparkpost, ActiveCampaign, AWeber, or Emma.&lt;/p&gt;

&lt;p&gt;Fortunately, I had recently scanned the archives of Wes Bos’ web-dev blog posts, and remembered the catchy title “From MailChimp to Sendy — how I saved $2,400/year on my email list”. With my relatively small email list, the change wasn’t motivated by saving money, but by hosting my own list to avoid another surprise account deletion or other issue related to not controlling my own data. Wes’ article convinced me to &lt;a href="https://sendy.co/?ref=vvD4p"&gt;check out Sendy&lt;/a&gt;. It’s a self-hosted mailing list software solution. But his article didn’t describe how to install it.&lt;/p&gt;

&lt;p&gt;After purchasing it and combing through the product support forums, it looked like DigitalOcean was a great choice for hosting based on both price and features. While reading many, many pages of documentation and stack overflow posts,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I took detailed notes on my installation process so I could present a complete and detailed record of the steps you can take to get a running instance of Sendy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I will also cover how to &lt;strong&gt;secure&lt;/strong&gt; your Sendy server traffic with &lt;strong&gt;HTTPS&lt;/strong&gt; with a free Certbot certificate, and set up &lt;strong&gt;automated mysql database backups&lt;/strong&gt; to an &lt;strong&gt;AWS S3&lt;/strong&gt; bucket at the frequency of your choosing.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to — start here
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://sendy.co/?ref=vvD4p"&gt;Read about and purchase Sendy from the developer here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;If you don’t already have an account with DigitalOcean, &lt;a href="https://m.do.co/c/c29fea8989aa"&gt;signing up through this link&lt;/a&gt; will get you $100 in free credit to use over 60 days.&lt;/li&gt;
&lt;li&gt;You will need an AWS account.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since the developer of Sendy says “if it doesn’t work out, we’ll refund you”, you can try this whole experiment without financial risk. I set up my Sendy installation on a &lt;strong&gt;$5/month standard droplet&lt;/strong&gt;. The only other costs are the Amazon SES e-mailing, which is &lt;strong&gt;$0.10 per thousand&lt;/strong&gt; e-mails, and your Sendy software purchase which is a &lt;strong&gt;one time fee&lt;/strong&gt; with no additional costs until major version updates.&lt;/p&gt;

&lt;h4&gt;
  
  
  Caveats
&lt;/h4&gt;

&lt;p&gt;Nowhere again in this post will I use the word ‘simple’. But I do believe that following this guide will make your installation process as easy as possible.&lt;/p&gt;

&lt;p&gt;I recommend browsing it first, and then following the steps in the order they are presented. Make sure you have extra hairs available in case you need to pull some out.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I reinstalled Sendy by following and validating all of the instructions that I’ve documented here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The only constant is change, and you may find yourself needing to jump through some unexpected hoops, just as I did during my initial install process. Hopefully everything that I have documented will be current and useful for you. If you found something that has changed, please describe it in the comments and I will update accordingly.&lt;/p&gt;

&lt;p&gt;I have made extra sure to implement a quality guide that is up to modern standards by making sure to use some instances of dry humor, at least one sci-fi movie reference, and occasional emoji insertions. As an added bonus, I used the example domain &lt;a href="http://www.contoso.com"&gt;www.contoso.com&lt;/a&gt; throughout the guide. I also documented the actual keys/secrets, and ip addresses I used while setting this up so you won’t have the extra mental baggage of trying to parse the syntax of things like .&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I’ve put in the extra effort to describe various scenarios you may encounter, and also to describe why something is done so you’ll be able to make informed choices if some step has changed since I documented my process.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;This is totally doable&lt;/strong&gt; , so read this guide, warm up your google chops, and &lt;strong&gt;I wish you the best on your installation process!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It may be helpful to breathe deeply, drink water, and walk around every once in a while.&lt;/p&gt;

&lt;h3&gt;
  
  
  Documentation conventions
&lt;/h3&gt;

&lt;p&gt;Text inside a block like this represents something you should type or something visible on the browser page or terminal display.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Text inside a box like this represents the terminal display

$ This represents your local terminal (after $)

# This represents the remote shell (after #)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Sail the DigitalOcean: create your droplet (cloud based virtual machine)
&lt;/h3&gt;

&lt;p&gt;Log into your new account, and verify that you have the free $100 credit. If you are a new customer but you did not sign up through a referral link, send an email to customer service with the referral link. In my experience, they will credit you. I signed up right after finding somebody else’s referral link, and when I e-mailed it to them, they gave me the credit. If you need a referral link, here is mine: &lt;a href="https://m.do.co/c/c29fea8989aa"&gt;https://m.do.co/c/c29fea8989aa&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a new ‘project’&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/1024/1*D9a35tqzZqEbhA4Zxzh-ng.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1024/1*D9a35tqzZqEbhA4Zxzh-ng.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next window will ask you: &lt;strong&gt;Move resources into the new project?&lt;/strong&gt; click &lt;strong&gt;Skip for now&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/1010/1*ynuytaca0iVyMD8A9WS4MQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1010/1*ynuytaca0iVyMD8A9WS4MQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, create your droplet and I will detail the settings below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/536/1*Q86jeTB8V1ybHZy9XqvsEw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/536/1*Q86jeTB8V1ybHZy9XqvsEw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the blue button, or the green drop down&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/628/1*eGpMmA8BWfLV859fdjy03w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/628/1*eGpMmA8BWfLV859fdjy03w.png" alt=""&gt;&lt;/a&gt;Click Droplets and then follow the instructions below for settings&lt;/p&gt;

&lt;p&gt;The next set of options is Choose an image &lt;strong&gt;.&lt;/strong&gt; Select Marketplace&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/1024/1*3VSjcXhWM8vk2xyBYWhMNQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1024/1*3VSjcXhWM8vk2xyBYWhMNQ.png" alt=""&gt;&lt;/a&gt;For ease, we’re going to install on a preinstalled LAMP stack on Ubuntu&lt;/p&gt;

&lt;p&gt;At the time of writing, the LAMP Marketplace app was version 18.04. The number is the version of Ubuntu (flavor of linux distribution). Newer versions (higher numbers), if available, &lt;em&gt;may&lt;/em&gt; present a similar installation experience. If you have some experience working with the command line and aren’t afraid to google things, go ahead and install with the newer version.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/738/1*7k8tRHKAVv_5GjINRWPN9w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/738/1*7k8tRHKAVv_5GjINRWPN9w.png" alt=""&gt;&lt;/a&gt;Latest release at time of writing&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/1024/1*bLVE0BbvCK4_J8Kfo_hwsQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1024/1*bLVE0BbvCK4_J8Kfo_hwsQ.png" alt=""&gt;&lt;/a&gt;Select the Standard plan&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/104/1*Fbiab4msR040mKAqbDitGg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/104/1*Fbiab4msR040mKAqbDitGg.png" alt=""&gt;&lt;/a&gt;Select the left arrow to see smaller droplet sizes&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/372/1*4IaZyR9JWf9fpywsIuFxTg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/372/1*4IaZyR9JWf9fpywsIuFxTg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose a size.&lt;/strong&gt; For my currently puny mailing list, I’m confident with the Standard plan and the smallest size virtual machine and I chose $5/mo. In the future, you can scale cpu/ram up and down as you wish. You can scale disk size up, but not down again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add backups?&lt;/strong&gt; I chose not to, since you can make and store manual snapshots. However, automating backups on a $5/month container will only cost you $1/month, so you may opt for this in order to save having to remember to make snapshots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add block storage?&lt;/strong&gt; Not required for this tutorial&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose a data center region.&lt;/strong&gt; I chose New York. You may have legal obligations to host your data in a particular region depending on the type of data you are storing, so choose accordingly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/704/1*Dqt8lg6sfrdtGHuNCqx8Gg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/704/1*Dqt8lg6sfrdtGHuNCqx8Gg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Select additional options?&lt;/strong&gt; I left all of these unchecked&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add your SSH keys.&lt;/strong&gt; I did not do so, and therefore use a password when I sign in via SSH. The initial root password is sent via email. This tutorial discusses using the password, so skip that step for now. SSH keys allow you to log into the remote server without needing to authenticate with a password.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finalize and create&lt;/strong&gt;. Make sure that you are only deploying 1 droplet, and rename it something sane (such as sendy-droplet in my example below), and make sure it is in your newly created ‘project’ (which it will be, unless you already have multiple ‘projects’)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/1024/1*jSBpKlGDqx6tY-534KCtvg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1024/1*jSBpKlGDqx6tY-534KCtvg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the “Create” button. You will then be redirected to your ‘project’ page, and see a status bar of your droplet creation progress. It takes a couple minutes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/1024/1*Wy6kD5gCQd68DcSdaG9m3w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1024/1*Wy6kD5gCQd68DcSdaG9m3w.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Followed by its successful creation, your login details and password have been e-mailed to you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/1024/1*EPhslJl3aKANZwfRPe-Z9w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1024/1*EPhslJl3aKANZwfRPe-Z9w.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congratulations, your newly spawned creation is running in the cloud. Now perform a sanity check by entering the ip address into your browser and you should see something like the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/1024/1*bWamT-2YYLby1oJgLO6N3g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1024/1*bWamT-2YYLby1oJgLO6N3g.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The “Quick”start Guide button &lt;a href="https://www.digitalocean.com/docs/one-clicks/lamp/#start"&gt;points to this document&lt;/a&gt; on the Digital Ocean website, which details what software your One-Click installation contains, as well as some configuration information.&lt;/p&gt;

&lt;h4&gt;
  
  
  Let’s log into the new droplet and set it up!
&lt;/h4&gt;

&lt;p&gt;You’re going to need the email you just received from DigitalOcean with the root password for your droplet. We will login with &lt;strong&gt;ssh&lt;/strong&gt; (secure shell).&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ssh root@157.230.133.78
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Which will be greeted by the following appropriately paranoid message&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The authenticity of host '157.230.133.78 (157.230.133.78)' can't be established.
ECDSA key fingerprint is SHA256:0U9apoBN+7h1ajk2+KZoYQiL35X4Zjp+9MQLC71mX7y.
Are you sure you want to continue connecting (yes/no)?
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You do want to continue connecting, so type yes and you should then see the following:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Warning: Permanently added '157.230.133.78' (ECDSA) to the list of known hosts.

root@157.230.133.78's password:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The terminal will not show the characters you enter when you type or paste the password. Enter the password and then press return and you should be logged in and see some boilerplate text describing some of the default configurations of your server.&lt;/p&gt;

&lt;p&gt;You will be prompted immediately to reset the root password. First enter the current password that you just used. Then enter a new secure password that you immediately record somewhere safe. You will need to enter your new password again right away to confirm, and then you will be logged into your droplet, a Linux Ubuntu virtual machine. The normal pwd ls cd mkdir etc terminal commands work as you would expect.&lt;/p&gt;

&lt;h4&gt;
  
  
  Update the list of available software
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# apt-get update
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;will update the list of available packages and their versions, but not install or upgrade any packages. It will create output similar to what is shown next:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Get:1 [http://security.ubuntu.com/ubuntu](http://security.ubuntu.com/ubuntu) bionic-security InRelease [83.2 kB]
Hit:2 [http://ppa.launchpad.net/certbot/certbot/ubuntu](http://ppa.launchpad.net/certbot/certbot/ubuntu) bionic InRelease

\*\*\*[Lots more stuff …]\*\*\*

Get:37 http://mirrors.digitalocean.com/ubuntu bionic-backports/main Translation-en [448 B]
Get:38 http://mirrors.digitalocean.com/ubuntu bionic-backports/universe amd64 Packages [3468 B]
Fetched 14.0 MB in 5s (3035 kB/s)
Reading package lists... Done
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Upgrade available packages
&lt;/h4&gt;

&lt;h1&gt;
  
  
  apt-get upgrade
&lt;/h1&gt;

&lt;p&gt;Accept the confirmation message and you will be updated to the latest version of all installed packages.&lt;/p&gt;

&lt;h4&gt;
  
  
  Install some new software needed by Sendy
&lt;/h4&gt;

&lt;p&gt;Next we need to install a couple software packages that are not part of the default 1-click installation, but will be needed by Sendy. The first one is php-curl&lt;/p&gt;

&lt;h4&gt;
  
  
  figure out which major.minor version of curl you need
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php --version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;will return a bunch of output including the (major.minor.patch) version that is installed on your server. In my case it’s 7.2.10 which we can use in the next command, but we only need the [major.minor] numbers.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# apt-get install php7.2-curl
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If in doubt what version to install, type # apt-get install php7. and then press tab a couple times to see an auto completion of the various software packages that are available for the major version of php that you are using. Mine auto-completed with # apt-get install php7.2-curl and then I clicked enter to install and also accepted the confirmation dialogue that may pop up.&lt;/p&gt;

&lt;p&gt;Here’s the next one to install:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# apt-get install php-xml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;accept the confirmation dialogue that may pop up and then restart the droplet’s web server with:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# systemctl restart apache2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Next, Create New Virtual Host Files
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Virtual host files are the files that specify the actual configuration of our virtual hosts and dictate how the Apache web server will respond to various domain requests.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In English: we need to set up the server to respond to the domain name that you are going to assign to it when you create a DNS record with your domain registrar. Example: if you run the website &lt;a href="http://www.contoso.com,"&gt;www.contoso.com,&lt;/a&gt; and you plan to install Sendy at sendy.contoso.com, you need to configure the apache web server on the droplet to serve the appropriate files when you visit the sendy domain. These files are hosted in /var/www/&lt;/p&gt;

&lt;h4&gt;
  
  
  Create The Directory Structure for the files to be served
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# mkdir -p /var/www/contoso.com/public\_html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  Create New Virtual Host Files
&lt;/h4&gt;

&lt;p&gt;These are the files that tell the Apache web server how to respond to web requests. We are going to copy and configure the default host file (named 000-default.conf). The following shell command is one line&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# cp /etc/apache2/sites-available/000-default.conf /etc/apache2/sites-available/contoso.com.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now are going to edit this file using nano or vim. If you’ve never used vim, I would not start now. Nano is fairly user-friendly and responds to arrow key movements to move the cursor around the screen just like a familiar text editor. &lt;strong&gt;After editing, press&lt;/strong&gt;  &lt;strong&gt;control and&lt;/strong&gt;  &lt;strong&gt;x together to save the file and return to the terminal.&lt;/strong&gt;&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# nano /etc/apache2/sites-available/contoso.com.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The file that appears should begin with&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;VirtualHost \*:80&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;and end with&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Don’t worry too much about what’s in the middle. It may vary a bit from what you see below.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Leave everything else as it is. We are going to add one directive (line of configuration code) after the  tag:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ServerName sendy.contoso.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;and modify two directives (lines of configuration code). First, change theDocument Root directive to:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DocumentRoot /var/www/contoso.com/public\_html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;and then remove the word “Indexes” from the line that looks like this:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Options Indexes FollowSymLinks
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;so that after you edit it looks like this:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Options FollowSymLinks
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And then also modify the text inside the  tag to:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Directory /var/www/contoso.com/public\_html/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The final result should be something similar to the following. Don’t worry about all the other lines matching exactly:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;VirtualHost \*:80&amp;gt;
        ServerName sendy.contoso.com
        ServerAdmin webmaster@localhost
        DocumentRoot /var/www/contoso.com/public\_html

        &amp;lt;Directory /var/www/contoso.com/public\_html/&amp;gt;
            Options FollowSymLinks
            AllowOverride All
            Require all granted
        &amp;lt;/Directory&amp;gt;

        ErrorLog ${APACHE\_LOG\_DIR}/error.log
        CustomLog ${APACHE\_LOG\_DIR}/access.log combined

        &amp;lt;IfModule mod\_dir.c&amp;gt;
            DirectoryIndex index.php index.pl index.cgi index.html index.xhtml index.htm
        &amp;lt;/IfModule&amp;gt;

&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is what mine looks like, but I’m installing Sendy as a subdomain of my personal website domain. If you plan to run Sendy as a standalone server (something like &lt;a href="http://www.contoso.com"&gt;www.contoso.com),&lt;/a&gt;,) then your final result should probably be: (notice the extra directive of ServerAlias)&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;################# WHEN NOT INSTALLING AS A SUBDOMAIN

&amp;lt;VirtualHost \*:80&amp;gt;
        ServerName contoso.com
        ServerAlias www.contoso.com
        ServerAdmin webmaster@localhost
        DocumentRoot /var/www/contoso.com/public\_html

        &amp;lt;Directory /var/www/contoso.com/public\_html/&amp;gt;
            Options FollowSymLinks
            AllowOverride All
            Require all granted
        &amp;lt;/Directory&amp;gt;

        ErrorLog ${APACHE\_LOG\_DIR}/error.log
        CustomLog ${APACHE\_LOG\_DIR}/access.log combined

        &amp;lt;IfModule mod\_dir.c&amp;gt;
            DirectoryIndex index.php index.pl index.cgi index.html index.xhtml index.htm
        &amp;lt;/IfModule&amp;gt;

&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If you edited using nano, when you use control + x to write and exit, you will confirm Save modified buffer? with Y and then enter again to accept the File Name to Write.&lt;/p&gt;

&lt;h4&gt;
  
  
  Activate The Virtual Host File (and deactivate the default host file)
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# a2ensite contoso.com.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;this will show some confirmation message including a notification about restarting apache. We will do that in a moment.&lt;/p&gt;

&lt;p&gt;First you need to deactivate the default virtual host file with:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# a2dissite 000-default.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;this will also show some confirmation message.&lt;/p&gt;

&lt;p&gt;And as the confirmation messages stated, it is now time to reload the Apache server to enable the new Apache configurations.&lt;/p&gt;

&lt;h4&gt;
  
  
  Reload the Apache server
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# systemctl reload apache2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This reloads the Apache web server only , and you should remain logged into the shell.&lt;/p&gt;

&lt;p&gt;If you visit the ip address now, you should see something like the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/852/1*0hyK-SBJ57KhFMVwE5io3A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/852/1*0hyK-SBJ57KhFMVwE5io3A.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Which is what we want. This is the result of removing Indexes from the Options directive inside of the  tag.&lt;/p&gt;

&lt;p&gt;**If you see something like this:**…&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/676/1*4AApNkI--SlfhmnN8u5T1w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/676/1*4AApNkI--SlfhmnN8u5T1w.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;…then the Options directive wasn’t properly updated. Check the syntax above. After you correct it, restart Apache with # systemctl reload apache2&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When you reload the browser, it should show the correct Forbidden message. This is going to keep the public from peering into your /uploads folder after you install Sendy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Regarding HTTPS traffic to the droplet
&lt;/h4&gt;

&lt;p&gt;Since the 1-click droplet is preconfigured with Certbot, securing your servers web traffic with SSL can be done with just a few commands. However, &lt;strong&gt;before we generate a certificate for the server, we need to set up DNS routing to the server so that we make sure we’re generating the certificate for the right domain.&lt;/strong&gt; Detailed HTTPS instructions will follow, at first we will cover how to get the proper DNS records configured for your droplet.&lt;/p&gt;

&lt;h3&gt;
  
  
  DNS Routing
&lt;/h3&gt;

&lt;p&gt;Log into your domain name registrar so that we can create some records that will point to your new server.&lt;/p&gt;

&lt;p&gt;In my case, for this example server, I would be adding a new record for the DNS records of my domain contoso.com. I would create an “A” record. The A name record would be: sendy, and the value: 157.230.133.78&lt;/p&gt;

&lt;p&gt;I just tested this out with my registrar, and within a couple minutes my subdomain resolved to the server and I saw the same image as when accessing the ip address directly.&lt;/p&gt;

&lt;p&gt;If you plan to install a standalone server without a subdomain, then the name of your “A” record would be www, and you would create a second “A” record for the bare root domain (contoso.com in this example). The syntax for this differs by registrar, and may be “@”, or sometimes just a blank field. The value of both of these “A” records would be the ip address of your droplet.&lt;/p&gt;

&lt;p&gt;Ideally it will only take a few minutes for your DNS changes to propagate. Feeling impatient or wondering if it worked? Check this out (and update it with your own domain name):&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[https://www.whatsmydns.net/#A/sendy.contoso.com](https://www.whatsmydns.net/#A/sendy.contoso.com)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is a pretty cool resource that shows you the status of the DNS propagation. My changes made it around the world in just a few minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enable HTTPS with SSP certificate installation with Certbot
&lt;/h3&gt;

&lt;p&gt;As part of your 1-click installation, there is an automated SSL certificate generator already installed called Certbot. Now that we have the virtual host files set up correctly, and DNS records correctly configured we can run it.&lt;/p&gt;

&lt;p&gt;For my example server, I only need to generate a certificate for the sendy subdomain, and this is what I would run:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# certbot --apache -d sendy.contoso.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If you are running it for a standalone Sendy installation, you will run it as follows (for the www subdomain, and root domain):&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# certbot --apache -d contoso.com -d [www.contoso.com](http://www.contoso.com)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After you run the command, you will be asked&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Enter email address (used for urgent renewal and security notices) (Enter 'c' to cancel):
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I personally consider entering an email address that you actually check a very good idea so that you can receive notifications in case the automated SSL certificate renewal process doesn’t work. If your certificate expires, browsing to your formerly secure site will not show your site, but instead a “Your Connection Is Not Private” warning page.&lt;/p&gt;

&lt;p&gt;If you choose to generate your certificate without the email address you can do so with the command that’s given after you press c at that prompt.&lt;/p&gt;

&lt;p&gt;After this step, you will be given the terms of service link and asked to agree. If you agree, you will be asked whether or not you want to share your email address with EFF.&lt;/p&gt;

&lt;p&gt;After you answer that, you will see a bunch of output related to the installer trying to access your domain. If your DNS propagation has not completed, or there was an error with how you entered your DNS records, this step will fail.&lt;/p&gt;

&lt;p&gt;The output so far should be something like:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@sendy-droplet:~# certbot --apache -d sendy.contoso.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator apache, Installer apache
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for sendy.contoso.com
Enabled Apache rewrite module
Waiting for verification...
Cleaning up challenges
Created an SSL vhost at /etc/apache2/sites-available/contoso.com-le-ssl.conf
Enabled Apache socache\_shmcb module
Enabled Apache ssl module
Deploying Certificate to VirtualHost /etc/apache2/sites-available/contoso.com-le-ssl.conf
Enabling available site: /etc/apache2/sites-available/contoso.com-le-ssl.conf

Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel):
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;At this point you are asked whether or not you also want to allow HTTP traffic to your server, or redirect it to HTTPS. I selected 2 and had the installer create a redirect from HTTP to HTTPS. When it comes time to configure Sendy, you can specify whether or not you prefer HTTP or HTTPS. Your Sendy configuration file must specify &lt;em&gt;one or the other&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Here is the remaining output:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Enabled Apache rewrite module
Redirecting vhost in /etc/apache2/sites-enabled/contoso.com.conf to ssl vhost in /etc/apache2/sites-available/contoso.com-le-ssl.conf

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations! You have successfully enabled
https://sendy.contoso.com

You should test your configuration at:
https://www.ssllabs.com/ssltest/analyze.html?d=sendy.contoso.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/sendy.contoso.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/sendy.contoso.com/privkey.pem
   Your cert will expire on 2019-04-12. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot again
   with the "certonly" option. To non-interactively renew \*all\* of
   your certificates, run "certbot renew"
 - If you like Certbot, please consider supporting our work by:

Donating to ISRG / Let's Encrypt: [https://letsencrypt.org/donate](https://letsencrypt.org/donate)
   Donating to EFF: [https://eff.org/donate-le](https://eff.org/donate-le)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If you want to feel extra secure that this worked, go ahead and follow the link to the test site that was just output after the You should test your configuration at: phrase. For this site it would be: &lt;a href="https://www.ssllabs.com/ssltest/analyze.html?d=sendy.contoso.com"&gt;https://www.ssllabs.com/ssltest/analyze.html?d=sendy.contoso.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now refresh or navigate to your server url, with your browser (go to your site, such as &lt;a href="https://www.contoso.com,"&gt;https://www.contoso.com,&lt;/a&gt; NOT the ip address), and you should see the:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/172/1*8yQbWEAzfX2amtg6hxofwQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/172/1*8yQbWEAzfX2amtg6hxofwQ.png" alt=""&gt;&lt;/a&gt;💥&lt;/p&gt;

&lt;p&gt;Your secure server is up and rolling!&lt;/p&gt;

&lt;p&gt;Let’s take a look at the ssl site configuration .conf file and make sure it properly copied over the directives from the original non-ssl version.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# nano /etc/apache2/sites-available/contoso.com-le-ssl.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Should bring up a page that looks pretty much like this. Look to see that the ServerName / DocumentRoot and Directory directives look similar. And also that the Options directory doesn’t have the Indexes keyword. If this .conf file wasn’t copied properly from the http version, you are currently in the nano editor, and can make the necessary changes now. Exit out of the editor with ctrl + x and you will return to the ssh terminal.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;IfModule mod\_ssl.c&amp;gt;
&amp;lt;VirtualHost \*:443&amp;gt;
        ServerName sendy.contoso.com
        ServerAdmin webmaster@localhost
        DocumentRoot /var/www/contoso.com/public\_html

        &amp;lt;Directory /var/www/contoso.com/public\_html/&amp;gt;
            Options FollowSymLinks
            AllowOverride All
            Require all granted
        &amp;lt;/Directory&amp;gt;

        ErrorLog ${APACHE\_LOG\_DIR}/error.log
        CustomLog ${APACHE\_LOG\_DIR}/access.log combined

        &amp;lt;IfModule mod\_dir.c&amp;gt;
            DirectoryIndex index.php index.pl index.cgi index.html index.xhtml index.htm
        &amp;lt;/IfModule&amp;gt;

SSLCertificateFile /etc/letsencrypt/live/sendy.contoso.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/sendy.contoso.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
&amp;lt;/VirtualHost&amp;gt;
&amp;lt;/IfModule&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Next is the initialization of the mysql database and then the upload of Sendy to your server.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing Your Mysql Database
&lt;/h3&gt;

&lt;p&gt;Surprise…it’s already installed. Welcome again to the joy of the 1-click droplet installation. We just need to create the Sendy database, a database user/password, and grant the new database user privileges to use the Sendy database.&lt;/p&gt;

&lt;p&gt;Login into mysql with # mysql and you should see something like:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.26-0ubuntu0.18.04.1 (Ubuntu)

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;and create your Sendy database (don’t forget the ; at the end)&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql&amp;gt; create database sendy;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;and you should see:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query OK, 1 row affected (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now we need to create a database user with a password, and grant them permission to use the Sendy database (remember the ;):&lt;/p&gt;

&lt;p&gt;Come up with a username and password and record it somewhere secure. You’re going to need it again soon.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql&amp;gt; CREATE USER 'sendy\_db\_admin'@'localhost' IDENTIFIED BY '2zrQYe5NC8z2sw87TpC4SWDK';
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;which should respond with:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query OK, 0 rows affected (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now we grant them privileges (remember the ; at the end):&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql&amp;gt; GRANT ALL PRIVILEGES ON \*.\* TO 'sendy\_db\_admin'@'localhost';
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;again which should respond with:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query OK, 0 rows affected (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Our work is done here. Exit the mysql command line interface with&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql&amp;gt; exit;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;and the terminal will say Bye. Now you are back at the prompt for the root user inside the droplet.&lt;/p&gt;

&lt;p&gt;You now have your database name, authorized user and password, and you can set up your Sendy configuration file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up Your Sendy Configuration File
&lt;/h3&gt;

&lt;p&gt;Assuming you have purchased and downloaded Sendy, unzip the file and edit /includes/config.php in your code editor.&lt;/p&gt;

&lt;p&gt;This is the part that we’re going to modify:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/\* Set the URL to your Sendy installation (without the trailing slash) \*/

define('APP\_PATH', 'http://your\_sendy\_installation\_url');

 /\* MySQL database connection credentials (please place values between the apostrophes) \*/

$dbHost = ''; //MySQL Hostname
 $dbUser = ''; //MySQL Username
 $dbPass = ''; //MySQL Password
 $dbName = ''; //MySQL Database Name
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The relevant values for the install in this tutorial would be:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/\* Set the URL to your Sendy installation (without the trailing slash) \*/

define('APP\_PATH', 'https://sendy.contoso.com');

 /\* MySQL database connection credentials (please place values between the apostrophes) \*/

$dbHost = 'localhost'; //MySQL Hostname
$dbUser = 'sendy\_db\_admin'; //MySQL Username
$dbPass = '2zrQYe5NC8z2sw87TpC4SWDK'; //MySQL Password
$dbName = 'sendy'; //MySQL Database Name
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;'localhost'will be the Hostname regardless of the other values. Enter the username/password for the database user we just created in the last step. And enter the database name you entered in the previous step, which in the demo was'sendy'.&lt;/p&gt;

&lt;p&gt;Notice: since we installed the SSL certificate, the ‘APP_PATH’ value has been set to https, and there is no trailing /after the .com&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Did you enter a different domain when you purchased Sendy than what you are going to end up using? No problem, just update your domain at the “ &lt;strong&gt;Changing your licensed domain”&lt;/strong&gt; link sent to you in your purchase confirmation email. I’ve done this a couple times and the update was immediate.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It’s now time to upload Sendy to your server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Uploading Sendy To Your Server
&lt;/h3&gt;

&lt;p&gt;You can use sftp or rsync. Sftp is probably more intuitive with relatively simple documentation, so I will show the command for rsync. The following command will be issued from a new terminal window, not the ssh shell we’ve been using. The command should be entered as one line. The -ahrvz flags mean “archive/human-readable/recursive/verbose/compressed”. The first folder path is for your local Sendy folder (remember to include the trailing /) and the second location is the DocumentRoot web content folder inside your droplet, which we specified in the virtual host file we edited previously. You will be prompted to supply your droplet root password after issuing this command:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IMPORTANT: the following command must be entered in a new terminal window, not in the SSH shell that we have been issuing commands to the server with, (as specified by the&lt;/strong&gt; &lt;strong&gt;$ at the beginning of the command)&lt;/strong&gt;&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ rsync -ahrvz -e ssh /path/to/your/sendy/folder/ root@157.230.133.78:/var/www/contoso.com/public\_html/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This will be followed by a confirmation such as:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@157.230.133.78's password:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After you successfully enter the root password, you will see a massive file list of everything that will be transferred to the server. The listed order of the files may be different, but it should start with something like:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@157.230.133.78's password:
building file list ... done
./
.htaccess
\_compatibility.php
\_install.php
app.php
autoresponders-create.php
autoresponders-edit.php
autoresponders-emails.php
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;and end with something like:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;locale/en\_US/
locale/en\_US/LC\_MESSAGES/
locale/en\_US/LC\_MESSAGES/default.mo
locale/en\_US/LC\_MESSAGES/default.po
uploads/

sent 5.62M bytes received 18.78K bytes 1.00M bytes/sec
total size is 13.41M speedup is 2.38

$
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;IMPORTANT: now we’re going to issue commands to the SSH shell to the server again&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After uploading, you need to make sure that the permissions of the uploads folder is correctly set to 0777 . This can be achieved with&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# chmod 0777 /var/www/contoso.com/public\_html/uploads/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;. And you can check if this worked with&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ls -al /var/www/contoso.com/public\_html/uploads/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You should see something like&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;total 8
drwxrwxrwx 2 501 staff 4096 Jan 12 00:25 .
drwxr-xr-x 10 501 staff 4096 Jan 12 04:31 ..
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The important permission bits are the drwxrwxrwx on the same line as the .&lt;/p&gt;

&lt;p&gt;The other line relates to the permissions of the parent folder.&lt;/p&gt;

&lt;p&gt;If everything has gone according to plan so far, when you reload your browser that is pointing to your new server, you should be redirected to /_install.php.&lt;/p&gt;

&lt;p&gt;If you see the following, you’re walking the happy path:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/1024/1*awTDewELFa5RTx1Byemuig.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1024/1*awTDewELFa5RTx1Byemuig.png" alt=""&gt;&lt;/a&gt;💥&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you don’t, don’t worry.&lt;/strong&gt; It’s also possible that you might see something like the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/1024/1*Ube6hCFODiaYytYoKu8K6Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1024/1*Ube6hCFODiaYytYoKu8K6Q.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not to worry, no matter what is missing, it’s easy to correct. Following the instructions, we navigate to: /_compatibility.php?i=1 and see what needs to be installed. Your results may vary slightly, but likely not too much if you have followed along with this this guide from the beginning.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/818/1*Qkr327UIbw64Y5aPPpgoaQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/818/1*Qkr327UIbw64Y5aPPpgoaQ.png" alt=""&gt;&lt;/a&gt;🤔&lt;/p&gt;

&lt;p&gt;Whatever is missing, search the support forum at: sendy.co/forum/ by entering the error into the search box to figure out how to remedy it.&lt;/p&gt;

&lt;p&gt;After hunting down any possible remaining packages, your server compatibility checklist should all be green, score 10/10 and navigating to your Sendy url should show you the login page…&lt;/p&gt;

&lt;p&gt;…or possibly this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/698/1*21Ax0plWOoaGJ-8xbuMWtg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/698/1*21Ax0plWOoaGJ-8xbuMWtg.png" alt=""&gt;&lt;/a&gt;😶&lt;/p&gt;

&lt;p&gt;If this is what you see, you will need to make sure that the domain that you entered as your APP_PATH variable in /includes/config.php matches the domain that you are currently pointed to with your browser, as well as being the domain that you entered when you purchased the product. Domain changes can be accomplished easily. &lt;strong&gt;The link for that form is in the email you receive when purchasing the software&lt;/strong&gt;. In the form, you enter your license key and in theChange domain to field, enter the updated domain. You do not need to preface it with http(s) when you enter the domain (see below). However you do need the http(s):// preface when defining the APP_PATHvariable in the Sendy config file.&lt;/p&gt;

&lt;p&gt;What I would need to enter in that form to enable Sendy for the examples here would be:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/1024/1*vDgurnwxzJteeBkjnbqd0A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1024/1*vDgurnwxzJteeBkjnbqd0A.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the rest of the setup of Sendy, I leave you to the capable hands of the official getting started documentation. You can pick up at step 5. There is very important information regarding getting your Amazon Web Services IAM credentials set up properly, as well as information on increasing your SES Sending Limits. This usually takes &lt;em&gt;at least&lt;/em&gt; 2 days for a new account, so if you are browsing through this documentation and planning on installing this software in the near future:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I recommend you actually handle this part now so that when you are ready to install you don’t have to deal with the speed bump of waiting for AWS to approve your increased sending limits.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One related tip that I will share is that the first time you log into your Sendy dashboard, you might see this, &lt;strong&gt;even if you already had your SES limit raised&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/302/1*4ZkuvzQ6-zi9dH0lixfiIg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/302/1*4ZkuvzQ6-zi9dH0lixfiIg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you know that you are no longer in “Sandbox mode”, the fix is to go into /settings and make sure that this is set properly:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/580/1*HTm1NqUS7cj5x58JvAp7dA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/580/1*HTm1NqUS7cj5x58JvAp7dA.png" alt=""&gt;&lt;/a&gt;3 available region choices at the time of this writing&lt;/p&gt;

&lt;p&gt;So what’s left? &lt;strong&gt;Let’s set up automatic backups of your database, and automatic transfers of the backups to an AWS S3 container!&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Things Go Wrong. Protect Your Valuable Mailing List Data By Setting Up Automatic Backups And Automating Cloud Storage Of Those Backups.
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Setting Up Automatic Mysql Dumps
&lt;/h4&gt;

&lt;p&gt;Before we set up the scheduler, let’s create a storage directory for the backups and backup script. This command:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# mkdir /srv/db\_bak/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;will create the empty folder where we will have our database backups dumped into.&lt;/p&gt;

&lt;p&gt;Let’s create our actual backup script with the nano editor again:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# nano /srv/dump\_script.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Paste the following into the editor and press Esc and then $ to get the lines to word-wrap so you can see everything on the screen at once. For the first chunk, you will substitute the database username and password you created previously. The second line copies the uploads folder to your backup directory. It’s good to have a backup of that, as any attachments or images that you upload via Sendy when creating campaigns will be stored in this folder, and the e-mails you send out will be served from your droplet. In case you are sending out massive email campaigns, you will probably want to link to images and uploads served via a CDN, or S3 bucket, and not put undue stress on your server.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/usr/bin/mysqldump -u'sendy\_db\_admin' -p'2zrQYe5NC8z2sw87TpC4SWDK' sendy &amp;gt; /srv/db\_bak/$(
date +%F)\_sendy\_backup.sql

cp -r /var/www/contoso.com/public\_html/uploads/ /srv/db\_bak/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;now control + x to exit, and confirm you want to save the changes, and confirm the file name. Now to configure the scheduler.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/572/1*5hZGqnUmGduzmkN0wISlTQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/572/1*5hZGqnUmGduzmkN0wISlTQ.png" alt=""&gt;&lt;/a&gt;No…Cron&lt;/p&gt;

&lt;p&gt;There is a fantastic utility that lives in the Unix-like world, including your droplet, called Cron. We’re going to use it to set up two jobs. One will take periodic snapshots of your database. The other will periodically transfer those snapshots to a secure cloud storage bucket. I’m going to show you how to transfer those backups to an AWS S3 bucket.&lt;/p&gt;

&lt;p&gt;Initially there is no user configuration file, known as a crontab, for the cron scheduling utility on your computer. Here’s how to create one:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# crontab -e
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;which will probably be followed by:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;no crontab for root - using an empty one

Select an editor. To change later, run 'select-editor'.
  1. /bin/nano &amp;lt;---- easiest
  2. /usr/bin/vim.basic
  3. /usr/bin/vim.tiny
  4. /bin/ed

Choose 1-4 [1]:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Unless you already know basic vim commands, go ahead and select the default nano editor by pressing 1 and then enter. You will be greeted with this screen which shows the default file which is entirely commented out with “helpful” boilerplate&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Edit this file to introduce tasks to be run by cron.
#
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
#
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '\*' in these fields (for 'any').#
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
#
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
#
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 \* \* 1 tar -zcf /var/backups/home.tgz /home/
#
# For more information see the manual pages of crontab(5) and cron(8)
#
# m h dom mon dow command
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Let’s erase that boilerplate and replace it with this:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# backing up to /srv/db\_bak/
# every day at 8:12 utc, dump database
12 8 \* \* \* . /srv/dump\_script.sh &amp;gt; /dev/null 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Crontable scheduling syntax gets no awards for readability. Check out this site if you want to see more details about what those obscure symbols mean. &lt;a href="https://crontab.guru/#12_8_*_*_*"&gt;https://crontab.guru/#12_8_*_*_*&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/764/1*YzWjIvM9rA5appqDEAckbA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/764/1*YzWjIvM9rA5appqDEAckbA.png" alt=""&gt;&lt;/a&gt;click on the underlined “next” to expand the display&lt;/p&gt;

&lt;p&gt;This will run our database backup script every day at 08:12 UTC, which is currently just past midnight here on the West Coast of the US. I picked that time because it’s unlikely that there will be many people interacting with my site in a way that will access the database at that time, and I wanted it to not be exactly on a 5-minute interval since that’s when other scripts will run. Perhaps not necessary to schedule like that, but that’s how I was thinking about it.&lt;/p&gt;

&lt;p&gt;You can feel free to set that interval to be more frequent than every day, since the way it’s written, it will overwrite the previous file as long as the current day of the system date is the same. It is going to be saving the filenames as…&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db\_bak/2019–05–03\_sendy\_backup.sql
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;…auto-updating the file prefix as the date changes.&lt;/p&gt;

&lt;p&gt;I’m guessing you might want to see this actually work and not have to wait a day. If you want to update the cron scheduling interval &lt;strong&gt;temporarily&lt;/strong&gt; to see if it works, change the first part of the third line to */2 * * * * and it will run every two minutes. Also don’t forget the . after the last *. Without that, the dump script won’t run. When you save the file, it will ask you to confirm the save, and then confirm the file name. Confirm the temporary file name that it shows you, and after you accept, it will install a new crontab that you can access again for edits and updates with crontab -e. You can also inspect your crontab at any time with crontab -l. You can also change your preferred crontab editor with select-editor.&lt;/p&gt;

&lt;p&gt;If you remove the &amp;gt; /dev/null 2&amp;gt;&amp;amp;1 part from the end of the cron script, the output of the job will generate a system mail message each time it runs. You can check it with cat /var/mail/root and delete the mail messages with rm /var/mail/root.&lt;/p&gt;

&lt;p&gt;Since you’ll be back in your Crontab file soon after you set up Sendy, I will save you some time and give you the code for the cron jobs that you are going to be setting up in order to schedule campaigns, activate auto responders, and import subscription oriented csv files. Feel free to enter these now, or wait until you get into configuring your Sendy dashboard.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Five minute cron job to send scheduled campaigns
\*/5 \* \* \* \* php /var/www/contoso.com/public\_html/scheduled.php &amp;gt; /dev/null 2&amp;gt;&amp;amp;1

# One minute interval job for importing csv
\*/1 \* \* \* \* php /var/www/contoso.com/public\_html/import-csv.php &amp;gt; /dev/null 2&amp;gt;&amp;amp;1

# One minute interval job for activating auto responders
\*/1 \* \* \* \* php /var/www/contoso.com/public\_html/autoresponders.php &amp;gt; /dev/null 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Part 1 of the automation tasks complete! Next is getting this all transferred automatically to your S3 bucket.&lt;/p&gt;

&lt;h4&gt;
  
  
  Setting Up Automatic Transfers Of The Mysql Backups To The S3 Bucket
&lt;/h4&gt;

&lt;p&gt;First we need to create the AWS S3 bucket that we’re going to send the transfers into. From the S3 console, create a new bucket. The Region is arbitrary, unless you have a specific need to host your data in a particular region.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/308/1*XyIHLGHhOmF4_RcEmelwNA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/308/1*XyIHLGHhOmF4_RcEmelwNA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/428/1*jjVBZS3KEduXbxhOS3Gmrg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/428/1*jjVBZS3KEduXbxhOS3Gmrg.png" alt=""&gt;&lt;/a&gt;Name And Region Settings&lt;/p&gt;

&lt;p&gt;Then click next&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/160/1*fSrH0qyEPr6JUuQoZGm0Ag.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/160/1*fSrH0qyEPr6JUuQoZGm0Ag.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I didn’t need to change anything in the “configure options” panel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/197/1*BkukwiDOdcll9Sr5BNkdEA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/197/1*BkukwiDOdcll9Sr5BNkdEA.png" alt=""&gt;&lt;/a&gt;Just click Next again&lt;/p&gt;

&lt;p&gt;But you need to “Set permissions” appropriately so that you will be able to upload to the bucket&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/169/1*rEDWhKmLq_NceOjKObx8gQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/169/1*rEDWhKmLq_NceOjKObx8gQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/1024/1*iG3zDtqgqvW0azDZZOqEWA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1024/1*iG3zDtqgqvW0azDZZOqEWA.png" alt=""&gt;&lt;/a&gt;Uncheck all these options&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/114/1*MeH7hagH2EUtdv2agpuXVg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/114/1*MeH7hagH2EUtdv2agpuXVg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Review your options and press:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/276/1*W-eW52bG5Tw6bdPi_aTt9Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/276/1*W-eW52bG5Tw6bdPi_aTt9Q.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/1024/1*FYtEgjGL237xW_HJ_Cq7DQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1024/1*FYtEgjGL237xW_HJ_Cq7DQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then select your bucket, select &lt;strong&gt;Permissions&lt;/strong&gt; , and set the following &lt;strong&gt;Access Control List:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/996/1*YayL7V4CJJkDOtocQFiD7A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/996/1*YayL7V4CJJkDOtocQFiD7A.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/340/1*NshnRDFD-YLVWMfUrBmxwQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/340/1*NshnRDFD-YLVWMfUrBmxwQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/717/1*DqJllc3TsdICu1UHZJcHCw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/717/1*DqJllc3TsdICu1UHZJcHCw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/520/1*ycP7kkYcZgMoCJgiRPilYw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/520/1*ycP7kkYcZgMoCJgiRPilYw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/1024/1*pSbdilPmxpMi-L9aXXwgnQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1024/1*pSbdilPmxpMi-L9aXXwgnQ.png" alt=""&gt;&lt;/a&gt;“write objects” enabled for everyone&lt;/p&gt;

&lt;p&gt;Then select the “Overview” tab and create a folder&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/1013/1*YeVeq86o1ASEx-U04y_sOg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1013/1*YeVeq86o1ASEx-U04y_sOg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/160/1*xDgu2olMNPud0aC-KPHZ5g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/160/1*xDgu2olMNPud0aC-KPHZ5g.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/1024/1*2iEvsIE7RjRi7Yd1h7cJwQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1024/1*2iEvsIE7RjRi7Yd1h7cJwQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keep the bucket and folder name handy, you will need them soon.&lt;/p&gt;

&lt;p&gt;And that’s it for setting up the bucket. Now to create a user and give them appropriate permissions to store things in your bucket.&lt;/p&gt;

&lt;h4&gt;
  
  
  Setting Up An AWS User In IAM And Granting Them Permission To Access Your Buckets
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/438/1*IwspZylIJst2zVHs6XT4UQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/438/1*IwspZylIJst2zVHs6XT4UQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Head over to the AWS IAM &lt;a href="https://console.aws.amazon.com/iam/"&gt;https://console.aws.amazon.com/iam&lt;/a&gt;/ console and click on the Users tab over on the left. Click on Add user and give them a sensible name and Programmatic access. And then click Next : Permissions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/109/1*vDoRHMhiY19FIiTjdoQAHg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/109/1*vDoRHMhiY19FIiTjdoQAHg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/1024/1*3kjHx9aUjWjbLj-Eos2Bxw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1024/1*3kjHx9aUjWjbLj-Eos2Bxw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/196/1*IKH48fBirgqktYDrI1oeZw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/196/1*IKH48fBirgqktYDrI1oeZw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select Attached existing policies directly&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/804/1*fKMBZUPCtYrgyyBZxXs54Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/804/1*fKMBZUPCtYrgyyBZxXs54Q.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and in the search box enter: s3ReadOnly and give the user AmazonS3ReadOnlyAccess&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/840/1*pOu54eDfbu9NtJgzCb05oA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/840/1*pOu54eDfbu9NtJgzCb05oA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then click on Next: Tags. I did not enter anything here. So next click on Next: Review and if everything looks appropriate click on Create user&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/127/1*btdKVLeHM8XdSBjtIo3IJg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/127/1*btdKVLeHM8XdSBjtIo3IJg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/147/1*Is-OA-HnhgwZEHBJnhOrNw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/147/1*Is-OA-HnhgwZEHBJnhOrNw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/475/1*47ma5tdOVF99JX8GrjWeNA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/475/1*47ma5tdOVF99JX8GrjWeNA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/133/1*D03p3vYFHxxCchQ9BLtdFg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/133/1*D03p3vYFHxxCchQ9BLtdFg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make note of the Access Key Id and Secret access key values. You will need those soon, and this is the only time that you can access the Secret access key so if you need that value later, you will have to create a new key for this user.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/902/1*VyDUok3zSRHN_SjAPN-d0g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/902/1*VyDUok3zSRHN_SjAPN-d0g.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Heading into the home stretch!&lt;/p&gt;

&lt;h4&gt;
  
  
  Installing AWS S3 Utilities On Your Droplet To Handle The Transfer Of Your Database Backups To The S3 Bucket
&lt;/h4&gt;

&lt;p&gt;Back in your root SSH terminal connection, install the S3 utilities with:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# apt-get install s3cmd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Accept the installation confirmation, and then you will have these tools installed. Now you need to enter your credentials.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# s3cmd --configure
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;will bring up a dialogue that looks something like:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Enter new values or accept defaults in brackets with Enter.
Refer to user manual for detailed description of all options.

Access key and Secret key are your identifiers for Amazon S3. Leave them empty for using the env variables.
Access Key:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You will enter your Access Key, and Secret Key and for the rest of the values, just click enter to accept the default values unless there is something you want to explicitly set. At the end of the process, make sure that when it tests your credentials that it passes, and then agree to Save settings.&lt;/p&gt;

&lt;p&gt;Things will probably have looked something like this:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Default Region [US]:

Use "s3.amazonaws.com" for S3 Endpoint and not modify it to the target Amazon S3.
S3 Endpoint [s3.amazonaws.com]:

Use "%(bucket)s.s3.amazonaws.com" to the target Amazon S3. "%(bucket)s" and "%(location)s" vars can be used
if the target S3 system supports dns based buckets.
DNS-style bucket+hostname:port template for accessing a bucket [%(bucket)s.s3.amazonaws.com]:

Encryption password is used to protect your files from reading
by unauthorized persons while in transfer to S3
Encryption password:
Path to GPG program [/usr/bin/gpg]:

When using secure HTTPS protocol all communication with Amazon S3
servers is protected from 3rd party eavesdropping. This method is
slower than plain HTTP, and can only be proxied with Python 2.7 or newer
Use HTTPS protocol [Yes]:

On some networks all internet access must go through a HTTP proxy.
Try setting it here if you can't connect to S3 directly
HTTP Proxy server name:

New settings:
  Access Key: AKIAJ3O4AL4VMLGBEPAQ
  Secret Key: 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
  Default Region: US
  S3 Endpoint: s3.amazonaws.com
  DNS-style bucket+hostname:port template for accessing a bucket: %(bucket)s.s3.amazonaws.com
  Encryption password:
  Path to GPG program: /usr/bin/gpg
  Use HTTPS protocol: True
  HTTP Proxy server name:
  HTTP Proxy server port: 0

Test access with supplied credentials? [Y/n]
Please wait, attempting to list all buckets...
Success. Your access key and secret key worked fine :-)

Now verifying that encryption works...
Not configured. Never mind.

Save settings? [y/N]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Save the settings and you are finished with this.&lt;/p&gt;

&lt;p&gt;You can test it out now with the following command, assuming you have a db backup in that folder. If you don’t already have a backup file, do this first:&lt;br&gt;&lt;br&gt;
 # echo 'just a test file' &amp;gt; /srv/db_bak/testFile.txt and then:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;s3cmd sync /srv/db\_bak/ s3://contoso-sendy-backups/sql-dumps/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;which should result in some output like this:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@sendy-droplet:/srv# s3cmd sync /srv/db\_bak/ s3://contoso-sendy-backups/sql-dumps/
WARNING: Empty object name on S3 found, ignoring.
upload: '/srv/db\_bak/2019-01-12\_sendy\_backup.sql' -&amp;gt; 's3://contoso-sendy-backups/sql-dumps/2019-01-12\_sendy\_backup.sql' [1 of 1]
 7945 of 7945 100% in 0s 85.26 kB/s done
Done. Uploaded 7945 bytes in 1.0 seconds, 7.76 kB/s.
root@sendy-droplet:/srv#
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now checking my S3 bucket, I see that the contents of that folder transferred successfully&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/1024/1*5xU_uKKipomxWncc4vQ1mA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/1024/1*5xU_uKKipomxWncc4vQ1mA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Okay…let’s make this a job for Cron!&lt;/p&gt;

&lt;p&gt;Add this to your crontab with crontab -e&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#every day at 9:12 utc, synchronize .sql dumps with s3 bucket
12 9 \* \* \* s3cmd sync /srv/db\_bak/ s3://contoso-sendy-backups/sql-dumps/ &amp;gt; /dev/null 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This will send the db dump to S3 daily at 9:12 UTC. Again you might want to test this by setting the interval to something much lower (detailed in the first Cron section, above), don’t forget to set it back. I think a daily external cloud backup is a sane default. Your needs may differ.&lt;/p&gt;

&lt;h3&gt;
  
  
  Restoring Database From a Saved Backup
&lt;/h3&gt;

&lt;p&gt;So what do you do if a database disaster happens? If you set up the S3 automated backups as described above, you have a backup!&lt;/p&gt;

&lt;p&gt;Restoring your database can be achieved as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sign into your S3 console&lt;/li&gt;
&lt;li&gt;select the backup that you want to restore&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/318/1*f-SrhKJFVZ0egLpn5kit0Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/318/1*f-SrhKJFVZ0egLpn5kit0Q.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When you download the file, it will likely be downloaded as a .txt file. Just rename the file extension to .sql&lt;/li&gt;
&lt;li&gt;Send the backup to your droplet from the command line with (this is all one line):&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ rsync -ahrvz -e ssh /path/to/your/sql/backup/2019-01-14\_full\_sendy.sql root@157.230.133.78:/srv/db\_bak/backup\_to\_restore.sql
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now login to your droplet and login to mysql to drop the existing corrupt database. &lt;strong&gt;**THIS WILL IRREVERSIBLY DESTROY THE CURRENT DATABASE**&lt;/strong&gt; So make sure you are only doing this if the db is already corrupt and needs to be restored from one of your backups.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# mysql
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;then ( &lt;strong&gt;this is the destructive part&lt;/strong&gt; ):&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql&amp;gt; drop database sendy;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;then re-create the sendy database&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql&amp;gt; create database sendy;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;then exit out of my sql with:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql&amp;gt; exit;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;and perform the database restoration from the remote shell with:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# mysql sendy &amp;lt; /srv/db\_bak/backup\_to\_restore.sql
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then refresh Sendy in your browser and you should be back where you started prior to the database corruption.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let’s Create A Snapshot Of The Droplet
&lt;/h3&gt;

&lt;p&gt;It’s nice to have an “undo” point. DigitalOcean gives you the ability to create snapshots of your virtual servers at any point in time. This would be a good time to create a snapshot, in case you ever need to restore the virtual server. If you did, you would only need to reimport your database to the latest saved version in your S3 bucket and you’d be back in business.&lt;/p&gt;

&lt;p&gt;To do this, first we need to shut down the droplet from the shell with:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# shutdown
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;this should give you some output similar to:&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Shutdown scheduled for Tue 2019-01-15 04:49:54 UTC, use 'shutdown -c' to cancel.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Your server is now going through the shutdown scripts, which is better than a hard power off. Now navigate to &lt;a href="https://cloud.digitalocean.com/droplets"&gt;https://cloud.digitalocean.com/droplets&lt;/a&gt; and select your Sendy droplet. You should see that it is currently off.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/106/1*yj4ZwLEYrsIGEtfwOj9vvA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/106/1*yj4ZwLEYrsIGEtfwOj9vvA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/116/1*N2H8RMKbQygr8BLb8olgIA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/116/1*N2H8RMKbQygr8BLb8olgIA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on the Snapshots tab and give your snapshot a sane name. Then click on Take snapshot and it will take a couple minutes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/909/1*kmEnP_uDVq1f5H0eje455g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/909/1*kmEnP_uDVq1f5H0eje455g.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the costs quoted at the time of this article, you’re looking at about $0.10 per month to store a snapshot.&lt;/p&gt;

&lt;p&gt;After the snapshot is complete, head back to the Power menu and turn your droplet back on. Give it a couple minutes to fully reboot before you try to reload, or you may see errors related to connecting to the server or database.&lt;/p&gt;

&lt;p&gt;You can take snapshots at any time like this, but of course be mindful of whether or not there are running jobs when you shut it down.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cdn-images-1.medium.com/max/605/1*2CUtloFHlWA61O2ngW1m5Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://cdn-images-1.medium.com/max/605/1*2CUtloFHlWA61O2ngW1m5Q.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Congratulations, That’s it!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Please comment on how it went! I would love to hear how your process went, and if this guide helped you.
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;I hope this guide saves you a lot of the headaches that I went through while setting this up. I’m happy knowing that it may have helped you, and if you want to say thank you, please leave a comment, &lt;a href="https://paypal.me/alexjacobs"&gt;and / or feel free to buy me a beer!&lt;/a&gt; 🍻 Cheers!&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>sendy</category>
      <category>mailchimp</category>
      <category>mailinglists</category>
      <category>awsses</category>
    </item>
    <item>
      <title>Kyle Simpson describes a paradigm shift in programming </title>
      <dc:creator>Alex Jacobs</dc:creator>
      <pubDate>Wed, 10 Apr 2019 06:17:18 +0000</pubDate>
      <link>https://forem.com/lexjacobs/kyle-simpson-describes-a-paradigm-shift-in-programming-3m47</link>
      <guid>https://forem.com/lexjacobs/kyle-simpson-describes-a-paradigm-shift-in-programming-3m47</guid>
      <description>&lt;h3&gt;
  
  
  this free video lecture is one of the best descriptions of coding I’ve ever seen. It also describes a fundamental shift in style we can make to improve software development as a whole.
&lt;/h3&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1115858817688965122-948" src="https://platform.twitter.com/embed/Tweet.html?id=1115858817688965122"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1115858817688965122-948');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1115858817688965122&amp;amp;theme=dark"
  }



&lt;/p&gt;

</description>
      <category>programming</category>
      <category>beginners</category>
      <category>codequality</category>
      <category>continuouslearning</category>
    </item>
    <item>
      <title>Calling out "Stunt JavaScript™️"</title>
      <dc:creator>Alex Jacobs</dc:creator>
      <pubDate>Fri, 22 Mar 2019 03:11:26 +0000</pubDate>
      <link>https://forem.com/lexjacobs/calling-out-stunt-javascript-3n1d</link>
      <guid>https://forem.com/lexjacobs/calling-out-stunt-javascript-3n1d</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Not only will we dissect the following function that is laden with es6+ syntax, but show how a humble es5 implementation is not only easier to read, but also more efficient.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Today, a student came to a mentor session wanting to dissect the following function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;omit&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;keyToOmit&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="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&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;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newObj&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;value&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;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;keyToOmit&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;newObj&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="nx"&gt;newObj&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;value&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;You could say that it functions like &lt;code&gt;filter&lt;/code&gt; for objects. The intended usage is to pass in an object as &lt;code&gt;obj&lt;/code&gt;, and a string as &lt;code&gt;keyToOmit&lt;/code&gt;, and to then return a new object with all but the omitted key/value pair.&lt;/p&gt;

&lt;p&gt;Like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;dog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;plays&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;guards&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;barksAllNight&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;dogUpgrade&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;omit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;barksAllNight&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ahh, now we can get some sleep.&lt;/span&gt;
&lt;span class="c1"&gt;// dogUpgrade =&amp;gt; { plays: true, guards: true }&lt;/span&gt;

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



&lt;p&gt;So to understand how it works, we went through the exercise of rewriting it into a less showy, but so much more clear, implementation of filtering out a key/value pair from the object.&lt;/p&gt;

&lt;p&gt;You may be wondering why we didn't just do something like&lt;br&gt;
&lt;code&gt;delete dog[keyToOmit]&lt;/code&gt;;&lt;br&gt;
Good question! It's because we don't want to 'mutate' the object that was originally passed into the function. Similar to the original &lt;code&gt;omit&lt;/code&gt; function, we wanted to return a fresh object that had all of the original key/value pairs, but not the key/value pair we want to filter out.&lt;/p&gt;
&lt;h3&gt;
  
  
  Simple Version
&lt;/h3&gt;

&lt;p&gt;Here is the rewrite we came up with. If you were having any trouble following the original function, see how this one compares when it comes to readability:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;omit&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;keyToOmit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt; &lt;span class="c1"&gt;// start with an empty object (don't mutate the original obj)&lt;/span&gt;
  &lt;span class="c1"&gt;// iterate and only add key/val pairs that are not the keyToOmit&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&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="k"&gt;if&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;!==&lt;/span&gt; &lt;span class="nx"&gt;keyToOmit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;result&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="o"&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;key&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;return&lt;/span&gt; &lt;span class="nx"&gt;result&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;Using a &lt;code&gt;for in&lt;/code&gt; loop to iterate through the keys of the object, we simply compare the &lt;code&gt;key&lt;/code&gt; to the &lt;code&gt;keyToOmit&lt;/code&gt;, and if it doesn't match, extend our &lt;code&gt;result&lt;/code&gt; object before returning it at the end.&lt;/p&gt;

&lt;p&gt;So now that we reverse engineered the function and made a basic implementation, we set out to decode the syntactic sugar sprinkled inside the original &lt;code&gt;omit&lt;/code&gt; function.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stunt Version
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Object.entries()&lt;/code&gt; is a fantastic method that returns nested arrays consisting of the key/value pairs of the object that is passed as an argument to the method.&lt;/p&gt;

&lt;p&gt;Invoking &lt;code&gt;Object.entries&lt;/code&gt; on our &lt;code&gt;dog&lt;/code&gt; would evaluate to&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dog&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// entries =&amp;gt; [["plays", true], ["guards", true], ["barksAllNight", true]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now that the key / value pairs are serialized within an array, we can use the HOF (higher order function) &lt;code&gt;reduce&lt;/code&gt; to boil the values down to a single final result.&lt;/p&gt;

&lt;p&gt;The function signature of the callback in &lt;code&gt;reduce&lt;/code&gt; is &lt;code&gt;(accumulator, currentValue, currentIndex, array)&lt;/code&gt;. The last 2 are optional, and not used in this function, so we'll ignore them for now, but it is good to know that they exist. You can read more &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce"&gt;about reduce on mdn here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, since we &lt;strong&gt;are&lt;/strong&gt; passing the first two arguments to &lt;code&gt;reduce&lt;/code&gt;, what are they in the context of the &lt;code&gt;omit&lt;/code&gt; function?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;plays&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;guards&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;barksAllNight&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newObj&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;value&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;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;keyToOmit&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;newObj&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="nx"&gt;newObj&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;value&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;The first argument &lt;code&gt;newObj&lt;/code&gt; corresponds to the accumulator. The second is written as &lt;code&gt;[ key, value ]&lt;/code&gt;. This is a nice use of &lt;em&gt;array destructuring assignment&lt;/em&gt; which allows for a concise assignment of 2 variables at once, corresponding to the array indices of the current value being passed to the reduce callback.&lt;/p&gt;

&lt;p&gt;Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;]&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="s1"&gt;first&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;second&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;// a =&amp;gt; 'first'&lt;/span&gt;
&lt;span class="c1"&gt;// b =&amp;gt; 'second'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So, since the reduce callback function is receiving another array with 2 values (key / value from the object) about the dog with each iteration, we can use destructuring to assign the variables &lt;code&gt;key&lt;/code&gt; and &lt;code&gt;value&lt;/code&gt; at once. Nice.&lt;/p&gt;

&lt;p&gt;Now, inside the &lt;code&gt;=&amp;gt;&lt;/code&gt; expression, we can refer to &lt;code&gt;newObj&lt;/code&gt; &lt;code&gt;key&lt;/code&gt; and &lt;code&gt;value&lt;/code&gt;. The ternary operator is set up so that if &lt;code&gt;key&lt;/code&gt; is exactly equal to &lt;code&gt;keyToOmit&lt;/code&gt;, we just return the accumulator unchanged. Otherwise, we use the object spread operator to succinctly copy the key/value pairs from the accumulator, and amend it with a new key/value pair. The &lt;code&gt;[key]: value&lt;/code&gt; syntax is yet another es6+ trick. It allows using a &lt;em&gt;Computed Property Name&lt;/em&gt; for the key. It evaluates the expression between the brackets, which is the &lt;code&gt;key&lt;/code&gt; variable. The string that evaluates to becomes the computed key that is added to the accumulator. The value of this key/value pair is the value of the &lt;code&gt;value&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;After the reduce function completes, the entire expression evaluates to the new object that is now missing the &lt;code&gt;keyToOmit&lt;/code&gt; and it is returned from the function.&lt;/p&gt;

&lt;h3&gt;
  
  
  It's super clever coding, but if used at scale, is quite inefficient.
&lt;/h3&gt;

&lt;p&gt;Let's take a look!&lt;/p&gt;

&lt;p&gt;Our lowly es5 implementation is less likely to get us labelled as &lt;code&gt;1337&lt;/code&gt; but let's consider its &lt;strong&gt;runtime complexity&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enter the function&lt;/li&gt;
&lt;li&gt;set up the results object&lt;/li&gt;
&lt;li&gt;iterate through the &lt;em&gt;enumerable properties&lt;/em&gt; of the object once

&lt;ul&gt;
&lt;li&gt;make a comparison of each value with the &lt;code&gt;keyToOmit&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;return the results object&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let's break down the steps required for the 'stunt JavaScript™️' version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enter the function&lt;/li&gt;
&lt;li&gt;derive &lt;code&gt;Object.entries&lt;/code&gt; by:

&lt;ul&gt;
&lt;li&gt;iterating through the &lt;em&gt;enumerable properties&lt;/em&gt; and assigning the key/value pairs to nested arrays&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;set up the reduce function&lt;/li&gt;
&lt;li&gt;iterate again through the derived nested array and

&lt;ul&gt;
&lt;li&gt;make the comparison of each value with the &lt;code&gt;keyToOmit&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;if it matches, return the accumulator unchanged&lt;/li&gt;
&lt;li&gt;otherwise iterate &lt;strong&gt;again&lt;/strong&gt; due to the object spread operator! (I think this pushes the time complexity closer to &lt;code&gt;O(n^2)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;return the evaluated result of the reduce function.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think that both are technically &lt;code&gt;O(n)&lt;/code&gt; run time complexity, but if you were to show a graph of both, they would both be linear, but the second version would be steeper in terms of operations per input size. And it is &lt;strong&gt;definitely&lt;/strong&gt; less efficient in terms of &lt;em&gt;space complexity (auxiliary space)&lt;/em&gt;. As a careful reader pointed out, if the implementation of the object spread operator truly iterates through each key value pair of the accumulator again, then this is now a nested for-loop. This would be an efficiency downgrade, closer to &lt;code&gt;O(n^2)&lt;/code&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Takeaway
&lt;/h3&gt;

&lt;p&gt;For a simple helper function, this time difference is immaterial, but I still think it's good to get in the habit of thinking about algorithmic complexity in order to build up intuition about runtimes in preparation for writing efficient code in the situations when it does matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final words
&lt;/h3&gt;

&lt;p&gt;As a teacher / mentor of newer engineers, I get a lot of input about what kinds of information is confusing to people who are earlier on in their programming journeys. I like to write about about these topics, to share these learnings with a wider community.&lt;/p&gt;

&lt;p&gt;I hope you've enjoyed coming along on this code exploration 🔎. More to come, so &lt;strong&gt;+ FOLLOW&lt;/strong&gt; and I'll see you further on down the code road!&lt;/p&gt;

&lt;h3&gt;
  
  
  Both functions side-by-side:
&lt;/h3&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/lexjacobs/embed/MxzxOg?height=600&amp;amp;default-tab=js&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.pexels.com/@webdonut"&gt;Thanks to Web Donut for the royalty free cover image&lt;/a&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>es6</category>
    </item>
    <item>
      <title>Conway's Game Of Life with different rules</title>
      <dc:creator>Alex Jacobs</dc:creator>
      <pubDate>Tue, 19 Mar 2019 20:11:58 +0000</pubDate>
      <link>https://forem.com/lexjacobs/conways-game-of-life-with-different-rules-13l0</link>
      <guid>https://forem.com/lexjacobs/conways-game-of-life-with-different-rules-13l0</guid>
      <description>&lt;h1&gt;
  
  
  Conway's Game Of Life
&lt;/h1&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life" rel="noopener noreferrer"&gt;The Game of Life, also known simply as Life, is a cellular automaton devised by the British mathematician John Horton Conway in 1970&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;If you've spent any time exploring or playing with Conway's Game Of Life, you're probably very familiar with resulting pattern such as:&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%2F58nwmmqorwdquwa6oohp.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%2F58nwmmqorwdquwa6oohp.png" alt="game of life representative end state"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  But what if you could play with the settings that determine the rules for each turn, and could create patterns such as this?
&lt;/h3&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%2F4epd8dl5u0pcem7b4xla.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%2F4epd8dl5u0pcem7b4xla.png" alt="game of life and state with different rules"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now you can!&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Here is a deployed version of the project that is &lt;a href="https://lexjacobs.github.io/conways-game-of-life-explorer/" rel="noopener noreferrer"&gt;up and running that you can play with&lt;/a&gt;
&lt;/h3&gt;

&lt;h3&gt;
  
  
  I've created a game of life explorer using JavaScript that lets you change the rules.
&lt;/h3&gt;

&lt;p&gt;The default rules are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Death by overpopulation if neighbors are greater than 3&lt;/li&gt;
&lt;li&gt;Death by under population if neighbors are less than 2&lt;/li&gt;
&lt;li&gt;Rebirth by reproduction if neighbors exactly equal 3&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've also created simple editing tools that let you add or delete individual cells, play and pause during the evolution of the game, move your cells around on the game board, and also select random starting patterns. You can also change your initial board size, and of the millisecond delay between turns.&lt;/p&gt;

&lt;p&gt;This allows you to make interesting starting patterns such as the following:&lt;br&gt;
(scroll to 30 seconds to see the pattern get much more complex and interesting)&lt;/p&gt;

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

&lt;h3&gt;
  
  
  You can explore the code at my repository at:
&lt;/h3&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/lexjacobs" rel="noopener noreferrer"&gt;
        lexjacobs
      &lt;/a&gt; / &lt;a href="https://github.com/lexjacobs/conways-game-of-life-explorer" rel="noopener noreferrer"&gt;
        conways-game-of-life-explorer
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Exploring Conway's Game of Life with variable board sizes, and alternate rules.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Learn more about Conway's Game of Life &lt;a href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life" rel="nofollow noopener noreferrer"&gt;at the wikipedia article here&lt;/a&gt;
&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Explore the Game &lt;a href="https://conways-game-of-life-explorer.herokuapp.com/" rel="nofollow noopener noreferrer"&gt;at the website deployed here&lt;/a&gt;
&lt;/h2&gt;

&lt;/div&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/lexjacobs/conways-game-of-life-explorer" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;I hope you enjoy exploring this project and enjoy learning more about The Game Of Life.&lt;/p&gt;

&lt;h3&gt;
  
  
  Please let me know what you think!
&lt;/h3&gt;

</description>
      <category>javascript</category>
      <category>automation</category>
      <category>opensource</category>
      <category>react</category>
    </item>
    <item>
      <title>I asked 12 software engineers how to get hired at their companies, and here’s what they said…</title>
      <dc:creator>Alex Jacobs</dc:creator>
      <pubDate>Wed, 12 Dec 2018 20:33:20 +0000</pubDate>
      <link>https://forem.com/lexjacobs/i-asked-12-software-engineers-how-to-get-hired-at-their-companies-and-heres-what-they-said-30ip</link>
      <guid>https://forem.com/lexjacobs/i-asked-12-software-engineers-how-to-get-hired-at-their-companies-and-heres-what-they-said-30ip</guid>
      <description>&lt;h4&gt;
  
  
  Lessons from a software engineering back-end recruitment “speed dating” style event in San Francisco
&lt;/h4&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;I feel that each company had their own take on the ideal path that could lead to getting hired at their company. If there was one major take away from the evening, I would say that the Silicon Valley/ Bay Area startup space seems to emphasize prior experience for their back end positions. If you are at a loss for how to get that initial experience, the advice that I heard more than once last night was to perhaps approach a larger tech company for an internship or apprenticeship.&lt;/p&gt;

&lt;p&gt;Don’t be discouraged though! Speaking from my own experience, and that of many of my colleagues, it is possible to get hired with a startup immediately after graduating from a boot camp, as I did in 2014.&lt;/p&gt;




&lt;p&gt;I’m not certain what keyword searches my various online engineering activities overlapped with, but this was the second time that I had been offered a free ticket to the HackerX recruiting event.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8gMzRyX3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AmA95Ct63gAiaGWmERzm2Vg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8gMzRyX3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AmA95Ct63gAiaGWmERzm2Vg.png" alt=""&gt;&lt;/a&gt;HackerX logo&lt;/p&gt;

&lt;p&gt;I didn’t attend the first time because I was not seeking additional employment, and also nearly declined on the event again until I read that they welcomed passive job seekers. Although I’m currently very happy with my engineering balance of teaching, mentoring, personal projects and study, I felt it would be valuable to learn more about the types of roles the industry is currently hiring for.&lt;/p&gt;

&lt;p&gt;After some brief presentations by the sponsoring companies, the “speed dating” portion of the evening began. As I was next in line to begin my first 5- minute interview, I realized the absurdity of the situation and quickly scrambled to figure out what would make the evening both entertaining and informative. I thought about my students and wondered what I could bring back to my students and mentees that could be useful. Out of my brain came the following question which I proceeded to ask for the remainder of the evening:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“[…quick personal introduction]. If you could reverse engineer the ideal employee for the positions you are hiring for, what skills and experience would you guide that candidate to gather right from the start of their learning path? Both soft and hard skills.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here’s a summary of what I learned, organized by the companies that I spoke to. Many of the companies also shared their opinions about boot camp graduates, as I had introduced myself as a technical mentor/teaching assistant for current boot camp students.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zsYAHliC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/838/1%2AYMRGG6on6lwgV0jD8Rep8Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zsYAHliC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/838/1%2AYMRGG6on6lwgV0jD8Rep8Q.png" alt=""&gt;&lt;/a&gt;PlayStation logo&lt;/p&gt;

&lt;h4&gt;
  
  
  PlayStation
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Most useful skill prior to applying: learn how to architect for distributed computing. The main question somebody should keep in mind while learning that skill is: “Can it scale? To millions?” Learn how to make applications that are simple and fast.&lt;/li&gt;
&lt;li&gt;The “unicorn” that this person was looking for would be an expert in databases who can also solve problems on a software and hardware level. Someone who can handle when stuff goes wrong. Also looking to hire someone who can remove busywork. Someone who is able to automate repetitive tasks.&lt;/li&gt;
&lt;li&gt;Looking for people with experience in building resilient applications. Can the systems handle failure?&lt;/li&gt;
&lt;li&gt;Most important soft skills: collaboration. This is useful in the industry in general. Can you comment effectively on others’ work? Conversely can you receive feedback gracefully?&lt;/li&gt;
&lt;li&gt;Most useful soft skill: the ability to defend your design. If you can’t clearly and logically describe your design from start to end, it probably won’t work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0_mxc31X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AwSwIz_cdSF2ujOlhXuOWyA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0_mxc31X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AwSwIz_cdSF2ujOlhXuOWyA.png" alt=""&gt;&lt;/a&gt;eatsa logo&lt;/p&gt;

&lt;h4&gt;
  
  
  Eatsa
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Values candidates having diverse skills backgrounds, which is often the case of boot camp graduates since they come from a variety of not-necessarily-technical backgrounds. They find that fresh perspective is even more useful than someone coming in with a CS college degree.&lt;/li&gt;
&lt;li&gt;Encourages generalists to apply, yet also wants to see evidence of depth. this could be as simple as being clear about what part of the stack you enjoy working on.&lt;/li&gt;
&lt;li&gt;In order to practice the skills that could be useful for a backend position with this company, building APIs would be good preparation as it provides a good balance of difficulty, yet allows for quick iterative development. Wants to see engineers that can produce results relatively quickly.&lt;/li&gt;
&lt;li&gt;Learn about professional engineering teams and how scrum works. They felt that many junior candidates without experience in engineering teams didn’t know how to leverage stand up as a chance to expose blockers and work as a team.&lt;/li&gt;
&lt;li&gt;Ideal prior experience: working with startups.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hntsy6Mn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/518/1%2Af0yAOmP_rJ2IFVGo4BJh2w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hntsy6Mn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/518/1%2Af0yAOmP_rJ2IFVGo4BJh2w.png" alt=""&gt;&lt;/a&gt;boxbot logo&lt;/p&gt;

&lt;h4&gt;
  
  
  Boxbot
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Useful hard skills: learn the popular frameworks/structures that are actively being developed in your major tech market, if any. For example: here in the Bay Area, they lean heavily on the Facebook React ecosystem.&lt;/li&gt;
&lt;li&gt;A startup often doesn’t have time to reinvent the wheel, so they seek to find engineers that are skilled in the locally developed popular frameworks and libraries.&lt;/li&gt;
&lt;li&gt;They mentioned another example that if you were in Seattle, learn to lean heavily on AWS infrastructure.&lt;/li&gt;
&lt;li&gt;Framework/library recommendations: React/Rails.&lt;/li&gt;
&lt;li&gt;Feelings about boot camp students: they have breath but no depth, yet.&lt;/li&gt;
&lt;li&gt;Recommended intermediary step prior to applying to this company: learn hard skills including deploying database frameworks such as GraphQL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VZh70PMd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/738/1%2AaAgGfRFqzC0ufnWs3XLv2g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VZh70PMd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/738/1%2AaAgGfRFqzC0ufnWs3XLv2g.png" alt=""&gt;&lt;/a&gt;thredUp logo&lt;/p&gt;

&lt;h4&gt;
  
  
  ThredUp
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Useful hard skills: mobile and full stack. Using Objective-C/swift for iOS. Using all Java for android, moving toward Kotlin next.&lt;/li&gt;
&lt;li&gt;Seeking applicants with full stack experience including high-level skills in Ruby on Rails and React.&lt;/li&gt;
&lt;li&gt;Soft skills: strong and cross collaborative. Ability to move fast. Ability to ship, even if fixes are required later. Not looking for super meticulous coders who unnecessarily delay pushing to production.&lt;/li&gt;
&lt;li&gt;Recommended intermediary step prior to applying to this company: they have had excellent applicants who had spent some time working or interning at Netflix.&lt;/li&gt;
&lt;li&gt;According to their Engineering101 document that they handed out, they value: People looking to make a quick impact / Elegant solutions that allow us to learn as a company / Paying off technical debt / People who can thrive in ambiguity / A drive to design and build solutions to meet customer needs / Cleaner implementations focusing on team efficiency, future speed, future capabilities, or scalability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0hWJ63if--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A5ZLlOfQub-Tdg9pyHrVVIQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0hWJ63if--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A5ZLlOfQub-Tdg9pyHrVVIQ.png" alt=""&gt;&lt;/a&gt;epam logo&lt;/p&gt;

&lt;h4&gt;
  
  
  epam
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Recommendations to all applicants: understand a company and its culture first.&lt;/li&gt;
&lt;li&gt;How they vet their engineers: initial skills match → technical interview → company culture fit interview&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qCJuylhq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A31RiDZ2AofknFTnBqjFTfg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qCJuylhq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A31RiDZ2AofknFTnBqjFTfg.png" alt=""&gt;&lt;/a&gt;hcl logo&lt;/p&gt;

&lt;h4&gt;
  
  
  HCL
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Soft skills: ability to learn new skills and be a good team player.&lt;/li&gt;
&lt;li&gt;Hard skills: considered very unimportant due to the number of different projects that they provide engineering services for.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dWoYlq5I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/proxy/1%2AwSwIz_cdSF2ujOlhXuOWyA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dWoYlq5I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/proxy/1%2AwSwIz_cdSF2ujOlhXuOWyA.png" alt=""&gt;&lt;/a&gt;eatsa logo&lt;/p&gt;

&lt;h4&gt;
  
  
  Eatsa (second conversation)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Hard skills: full stack. Knowing React is good, but they also want backend applicants that are skilled with Python/Ruby.&lt;/li&gt;
&lt;li&gt;Thoughts about boot camp grads: 60% of boot camp grads aren’t strong beyond the latest front end libraries such as React.&lt;/li&gt;
&lt;li&gt;Recommended intermediary step prior to replying to this company: internship experience. They mentioned some companies that offer internships or apprenticeship such as dropbox, lyft, or google. Go to a big company and spend some time developing your skills because training is not available at a small startup such as this one.&lt;/li&gt;
&lt;li&gt;Recommended amount of industry experience prior to applying here: 1 year&lt;/li&gt;
&lt;li&gt;Recommended portfolio items: evidence of shipping code. Boot camp projects might be impressive, but don’t show evidence of shipping production code.&lt;/li&gt;
&lt;li&gt;Interview skills: data structures/algorithms/big O notation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GsJiTAuE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AJOJtfUlATtp8Z12iFgFRIA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GsJiTAuE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AJOJtfUlATtp8Z12iFgFRIA.png" alt=""&gt;&lt;/a&gt;volta logo&lt;/p&gt;

&lt;h4&gt;
  
  
  Volta
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Thoughts about boot camp graduates: they may be up-to-date with the latest tech, but this may not be so important. They want applicants who have experience with architecture and business logic.&lt;/li&gt;
&lt;li&gt;Learn the tools that can allow a startup to remain small and lean as long as possible, such as learning faas (function as a service) such as aws lambda.&lt;/li&gt;
&lt;li&gt;Learn how to avoid putting bottlenecks into servers, such as offloading static assets.&lt;/li&gt;
&lt;li&gt;What are some other factors that make a great candidate for them: applicants that write technical blog posts on medium and actively contribute to open source.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>career</category>
      <category>motivation</category>
      <category>startup</category>
      <category>todayilearned</category>
    </item>
  </channel>
</rss>
