<?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: Lob</title>
    <description>The latest articles on Forem by Lob (@lob).</description>
    <link>https://forem.com/lob</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%2F706711%2F504ef930-f060-49d8-b0fe-e9bee63e3f61.png</url>
      <title>Forem: Lob</title>
      <link>https://forem.com/lob</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/lob"/>
    <language>en</language>
    <item>
      <title>To Infinity and Beyond: Our Nomad Migration is complete!</title>
      <dc:creator>Lob</dc:creator>
      <pubDate>Fri, 30 Dec 2022 21:34:46 +0000</pubDate>
      <link>https://forem.com/lob/to-infinity-and-beyond-our-nomad-migration-is-complete-3pg</link>
      <guid>https://forem.com/lob/to-infinity-and-beyond-our-nomad-migration-is-complete-3pg</guid>
      <description>&lt;p&gt;Lob’s core API has been fully migrated to HashiCorp's &lt;a href="https://www.nomadproject.io/" rel="noopener noreferrer"&gt;Nomad&lt;/a&gt;, Lob’s &lt;em&gt;Next Generation&lt;/em&gt; service platform. This is a major milestone for the Nomad Project, the Platform Team, and Lob Engineering. This migration is the culmination of a year of R&amp;amp;D, months of practice migrating other Lob services, and weeks of work on this particular service. It’s absolutely worth celebrating for the complexity and customer impact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TLDR: Lob’s core API has migrated from Convox to Nomad. Our API is running better than it was before and with Nomad we have the tools to tackle platform issues that were previously not possible.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;The way we run code for our API and dozens of supporting tasks has organically grown based on the needs and constraints of the business. Most apps at Lob ran on Convox, a straightforward service management tool we license, but a handful of workloads have needs that are not met by Convox! Historically, these run on AWS Lambda for security or scaling reasons, or Heroku for development ease, and some run on ECS for various customization needs.&lt;/p&gt;

&lt;p&gt;The point is that this artisanal, organic, Non-GMO architecture was and continues to be a huge burden on Lob Engineering. The Platform team needs to know half a dozen tools &lt;em&gt;very well&lt;/em&gt; and the rest of engineering must settle for a narrow feature set or spend weeks ramping up on a different technology. This drags on all new products, features, and bug fixes resulting in lower engineering velocity and a lot of hair-pulling.&lt;/p&gt;

&lt;p&gt;It was clear back in 2021 that Lob needed to consolidate how we run code and none of our current tools were up to the task; it wasn’t a matter of &lt;em&gt;if&lt;/em&gt; Lob would upgrade to something new, but &lt;em&gt;when.&lt;/em&gt; The Platform team kicked off a research project to find Lob’s next service platform. Forever ago (back in 2019) we investigated migrating to Kubernetes, a popular but notoriously difficult-to-manage tool for this sort of thing, but that project fizzled out for many reasons, forcing us to consider something else. &lt;a href="https://dev.to/lob/hard-pass-kubernetes-hello-nomad-38pn"&gt;We chose Nomad&lt;/a&gt; which offers a comparable feature set to Kubernetes in a much more streamlined package. Nomad is developed by &lt;a href="https://www.hashicorp.com/" rel="noopener noreferrer"&gt;HashiCorp&lt;/a&gt;, a leader in the DevOps space, and is used by companies like Pandora, Cloudflare, Internet Archive, and Roblox.&lt;/p&gt;

&lt;p&gt;Nomad is able to meet all of Lob’s business needs today and into the future, allowing us to consolidate where we run most of our code onto one platform. We are able to specialize in this tool, create custom workflows to meet Lob’s changing needs, and tune the underlying architecture to outperform our old solution(s). Where before we had to stretch ourselves thin, or live without some features, Nomad is a one-stop shop.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does this get us &lt;em&gt;today&lt;/em&gt;?
&lt;/h2&gt;

&lt;p&gt;Our core API running on Nomad has performance parity with our old container orchestrator, but it’s &lt;em&gt;even better&lt;/em&gt; in some ways. Here’s how:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lob releases happen via GitHub Actions, giving us better visibility into when the last release was, what got deployed, if it failed, and who kicked it off.&lt;/li&gt;
&lt;li&gt;Ad-hoc tasks like Database Migrations and Scripts are also managed in GitHub Actions, moving even more processes off of your laptop and onto the cloud.&lt;/li&gt;
&lt;li&gt;Nomad offers a much richer UI, enabling Lobsters to gain better visibility into how their app is working or debug their app when it isn’t.&lt;/li&gt;
&lt;li&gt;Nomad’s autoscaling is much more customizable, allowing us to scale our API more responsively to meet spikes in customer requests.&lt;/li&gt;
&lt;li&gt;Tag-driven releases allow us to audit when code was deployed and promoted to customers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What does this get us &lt;em&gt;tomorrow&lt;/em&gt;?
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;real&lt;/em&gt; reason to migrate to Nomad is for the laundry list of shiny new features that unlock major potential down the road…&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hosting containers on AWS Spot Instances should save us money on our AWS bill.&lt;/li&gt;
&lt;li&gt;Middleware for handling authentication and other business needs &lt;em&gt;&lt;a href="https://dev.to/lob/livin-on-the-edge-improve-developer-velocity-with-fastly-1f41"&gt;at the edge&lt;/a&gt;&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Canary deployments, finding regressions before rolling them out to all customers.&lt;/li&gt;
&lt;li&gt;Federated services, hosting our apps in AWS regions closer to our customers.&lt;/li&gt;
&lt;li&gt;Autoscaling based on custom metrics like workload prediction.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The final piece of the puzzle
&lt;/h2&gt;

&lt;p&gt;Most of our API’s migration to Nomad happened in August, but the key feature of &lt;strong&gt;&lt;em&gt;autoscaling&lt;/em&gt;&lt;/strong&gt; was not working as expected. This turned out to be a &lt;a href="https://github.com/hashicorp/nomad/issues/14915" rel="noopener noreferrer"&gt;bug in Nomad&lt;/a&gt; which James Douglas tracked down. The issue was recently fixed and autoscaling works as expected, completing the migration!&lt;/p&gt;

&lt;p&gt;This migration, as with all of Lob’s Nomad migrations, is intended to be &lt;em&gt;zero downtime.&lt;/em&gt; This means we would slowly migrate traffic to Nomad and roll back if we noticed any regressions in order to minimize customer impact; in most cases, customers cannot tell that this type of change took place. Autoscaling was an optional feature—we could just run Lob at max—but that would be a waste of resources and wouldn't really bring us to parity with the old Convox deployment.&lt;/p&gt;

&lt;p&gt;50+ services have now been migrated successfully!&lt;/p&gt;

&lt;h2&gt;
  
  
  Countless Kudos
&lt;/h2&gt;

&lt;p&gt;This is the type of project that cannot happen in a vacuum. The Platform team is incredibly thankful for all of the Lobsters past and present who have helped us out. You have lent us your time, expertise, patience, and trust in a project that really needed it. While it’s easy to move on to the next big project, moving our core API to Nomad is an accomplishment worth taking a moment to celebrate.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Special thanks to Senior Platform Engineer Elijah Voigt.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>management</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Know What You Shipped Last Summer</title>
      <dc:creator>Lob</dc:creator>
      <pubDate>Tue, 20 Dec 2022 17:56:39 +0000</pubDate>
      <link>https://forem.com/lob/i-know-what-you-shipped-last-summer-2abp</link>
      <guid>https://forem.com/lob/i-know-what-you-shipped-last-summer-2abp</guid>
      <description>&lt;p&gt;A hackathon is the perfect embodiment of several of Lob’s &lt;a href="https://www.lob.com/blog/core-values"&gt;core values&lt;/a&gt; including &lt;em&gt;Be bold&lt;/em&gt;, &lt;em&gt;Move fast, take action&lt;/em&gt;, and &lt;em&gt;Level up&lt;/em&gt;! It’s an opportunity to step outside the daily hustle, collaborate, innovate, and problem-solve. The only thing more fun than a hackathon is a themed hackathon—especially when the theme is Halloween!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PsfOO6bm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yd8sxv8wb4tifarkmoc7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PsfOO6bm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yd8sxv8wb4tifarkmoc7.png" alt="hackathon posters" width="691" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We want to acknowledge the hard work our Product and Engineering teams put in by highlighting their projects; the following is a technical recap of Lob’s Halloween Hackathon 2023.&lt;/p&gt;

&lt;h2&gt;
  
  
  SQS-consumer gets a glow up
&lt;/h2&gt;

&lt;p&gt;Many of our projects focused on improving the developer experience at Lob, but a few innovative Lobsters created a new open-source library that also benefits our community as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/sqs/"&gt;AWS SQS&lt;/a&gt;, or Simple Queue Service, is a piece of infrastructure offered by Amazon; it’s a powerful tool processing data in a controlled manner. “Sqs-consumer” is an existing open-source library for working with SQS.&lt;/p&gt;

&lt;p&gt;Though it’s a very popular library—we use it in Lob API and other services—it is not being maintained (major issues and suggested features are not getting attention) and perhaps most importantly, it’s not been upgraded to the new version of AWS SDK (V3). This prohibits us from upgrading many of our workers, and Lob API as a whole, to V3 of the SDK.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution: A new repo for working with SQS&lt;/strong&gt; &lt;a href="https://github.com/lob/sqs-consumer"&gt;&lt;strong&gt;@lob/sqs-consumer&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;!&lt;/strong&gt; It's an open fork of the previous package, but it makes a few key changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Upgrade underlying AWS SDK to V3&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Upgraded all dependencies&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Converted test suite from an outdated version of Mocha to Jest&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fixed challenges with FIFO queues&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even though we now offer a low-code solution to automate direct mail (via our &lt;a href="https://youtu.be/ZvST6thqO1I"&gt;Campaigns&lt;/a&gt; product), we will always be a developer-first company. This library is a big win for our engineers, and we look forward to feedback and contribution from the developer community.&lt;/p&gt;

&lt;p&gt;Software Engineer, Sishaar Rao (with help from Staff Software Engineer, Landon Barnickle, and Senior Software Engineer, Hanqing Chen) tied for second place for this ingenious project.&lt;/p&gt;

&lt;h2&gt;
  
  
  o11y is the bee’s knees
&lt;/h2&gt;

&lt;p&gt;Another hackathon project focused on increasing our understanding of the services at play at Lob. Staff Software Engineer Rich Sevoria and Senior Software Engineer Benny Kitchell teamed up to tackle observability.&lt;/p&gt;

&lt;p&gt;Lob’s rendering engine transforms HTML templates into print-ready PDFs, at scale. Every so often, we’ll encounter a glitch, and debugging can be a very manual—and painful—process. Most of the logging is contained within an internal service so any one individual lacks full visibility into all the services that are connected to each other and where they are failing. Ideally, you could visualize what's taking place, how long it takes, and in what order.&lt;/p&gt;

&lt;p&gt;Sevoria proposed a two-part solution.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;First, &lt;strong&gt;utilizing&lt;/strong&gt; &lt;a href="https://opentelemetry.io/"&gt;&lt;strong&gt;Open Telemetry&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;, or OTel, ”a vendor-neutral open-source Observability framework for instrumenting, generating, collecting, and exporting telemetry data such as traces, metrics, logs.”&lt;/strong&gt; Using traces and spans, we can get detail and visualization of all the events that took place across multiple services that aren't connected in any way outside of that they're all our services.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Then &lt;strong&gt;we can use&lt;/strong&gt; &lt;a href="https://www.honeycomb.io/opentelemetry"&gt;&lt;strong&gt;Honeycomb&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;for a deep-dive analysis of this information.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y8ijqo2T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uryzmsb48q59o1eewd87.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y8ijqo2T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uryzmsb48q59o1eewd87.png" alt="open telemetry architecture" width="880" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unlike &lt;a href="https://www.datadoghq.com/"&gt;Datadog&lt;/a&gt; (which we use at Lob quite a bit), you don't need to know what axis you want to analyze in advance. (Anybody who spends enough time with Datadog knows that if you wanted to, for example, search aggregate information by a given data set, you better have known that already and have created a facet for it; if you haven't, you're not going to be able to get an accurate read off of it.) You can aggregate on any dimension at any time you want, which is fantastic for analysis.&lt;/p&gt;

&lt;p&gt;Sevoria noted two other benefits of exploring Honeycomb for data analysis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;using industry-standard typically results in less technical debt (in form of a really tightly coupled adherence to a specific vendor's implementation), and&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Honeycomb has a very transparent and easy-to-understand pricing model.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Simplifying local development
&lt;/h2&gt;

&lt;p&gt;In addition to hops along the way to delivery, during creation, each mailpiece takes a trip through a network of distributed services. This means Lob services are difficult to stitch together when developing locally. We've historically called staging instances from our local environments to simulate the production behavior. Unfortunately, this process breaks down when the interaction is asynchronous or when multiple services need to be updated simultaneously.&lt;/p&gt;

&lt;p&gt;This problem was introduced with ominous music and masterful editing. Remember the scene from “Saw” when &lt;a href="https://www.youtube.com/watch?v=u7k8wUvn-88"&gt;Jigsaw&lt;/a&gt; says there is only one key? Masterful editing had Jigsaw following up with, “to find the key, you must integrate services in Docker.”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NnDKpGSj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2kupq2235zvnah8giagn.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NnDKpGSj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2kupq2235zvnah8giagn.jpeg" alt="jigsaw clown from Saw movies" width="600" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The team identified three reasons why it’s so difficult to integrate in Docker: differences in naming conventions, port bindings, and overlapping resources.&lt;/strong&gt; Lob uses AWS so we communicate with each other through queues. This can get quite complex, (previously queue messages are problematic for example), but the team uncovered a way to simplify it.&lt;/p&gt;

&lt;p&gt;They proposed standardizing starting conventions, staggering our ports, and sticking with one version (or one running container) for joint resources like one AWS stack and Redis.  &lt;strong&gt;Before vs. After:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_CJ3QldJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ot66t7vp81hl0ku9qovr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_CJ3QldJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ot66t7vp81hl0ku9qovr.png" alt="Image description" width="591" height="388"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7pUdIqvx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ih4tcs0704aapnfk9q1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7pUdIqvx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ih4tcs0704aapnfk9q1.png" alt="Image description" width="546" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each service can be started separately via a new startup script. This ensures shared localstack resources are started, adds the queues/buckets, etc., to the shared AWS localstack that is needed, and starts up only needed services in the Docker network: shared. Port binding conflicts can be avoided by referring to the new (very high-tech) shared Google spreadsheet.&lt;/p&gt;

&lt;p&gt;This has the potential to standardize how Lob’s apps communicate with each other. This aligns with similar initiatives within &lt;a href="https://www.lob.com/blog/improve-developer-velocity-with-fastly"&gt;Lob architecture&lt;/a&gt; and a continued push towards operating as a unit or as a whole ecosystem, not just individual teams.&lt;/p&gt;

&lt;p&gt;Major kudos to Staff Software Engineers Krys Flores, Ken Pflum, and Adrian Fallerio, Senior Software Engineer David Nutting, and Software Engineer Nick Perri—their efforts resulted in a tie for second place.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub productivity hack
&lt;/h2&gt;

&lt;p&gt;Easily winning the best costume award was Senior Software Engineer, David Nutting, for his “Lob” Zombie getup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nZTVTW9O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lwbdt9em7vwrl0hcussb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nZTVTW9O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lwbdt9em7vwrl0hcussb.png" alt="man in Rob Zombie costume" width="576" height="579"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nutting kicked off our presentations with a GitHub productivity hack. He outlined the frustrating and time-consuming scenario where you are waiting on your code to be approved, then manually executing a merge, only to be served with the  “This branch is out-of-date with the base branch” message (sometimes not just once, but twice).&lt;/p&gt;

&lt;p&gt;As it turns out, &lt;a href="https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/automatically-merging-a-pull-request"&gt;&lt;strong&gt;automatically merging a pull request&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;is a feature that's built right into GitHub.&lt;/strong&gt; To turn it on, you just have to open up the repo settings and check two boxes. That's it!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8IaWewTK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6rj8vujbqlgy41hyerqs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8IaWewTK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6rj8vujbqlgy41hyerqs.png" alt="this branch is out of date message" width="880" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Though not a feature of GitHub proper, there are also  Github Actions that will essentially press that “Update branch” button for you. So when any other merge happens to main, a "push" event will trigger some code to tickle your branch and pull new changes. (If you are concerned about future conflicts, you can set up as many notifications as you want in GitHub.)&lt;/p&gt;

&lt;p&gt;While better for some use cases than others, this certainly is a time-saver.&lt;/p&gt;

&lt;h2&gt;
  
  
  Changing of the guard
&lt;/h2&gt;

&lt;p&gt;Karmbir (KC) Chima was on-call for a portion of the hackathon; this ended up being the inspiration for his project. He too was interested in saving time. At the end of each on-call shift, the primary engineer—or “goalie”—is responsible for a comprehensive handoff. This involves documenting all issues (resolved and ongoing) and other detail; essentially duplicating all the info captured in the tickets. But let’s be honest, at the end of two weeks, not everyone has the time/energy/memory to do this at a high level. Realizing that &lt;a href="https://www.notion.so/integrations/jira-15d02cbd-b82a-4ccd-928e-f2ff0806f9ba"&gt;&lt;strong&gt;Notion can natively sync with Jira&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;,&lt;/strong&gt; the solution was obvious: Add an “on-call” label to the request form (a Slack Workflow that creates a Jira ticket); this automatically syncs into the Notion document, eliminating the need for recall and duplicate data entry.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Z4ji83_L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0z3mqhjmde9tm9as5kfu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Z4ji83_L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0z3mqhjmde9tm9as5kfu.png" alt="efficiency: toilet by a desk" width="355" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Run, Dora, run
&lt;/h2&gt;

&lt;p&gt;Staff Engineer Guy Argo also had efficiency on the brain. Lob’s address verification software, affectionately named “Dora,”  is &lt;a href="https://postalpro.usps.com/certifications/cass"&gt;CASS-certified&lt;/a&gt;, which is a huge benefit to our customers, but does require quite a bit of work to maintain. For the latest round of certification, we have about 150,000 tests to run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CT6H9uIm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m79yoyr9w31pff4rcszk.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CT6H9uIm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m79yoyr9w31pff4rcszk.jpeg" alt="skeleton at computer" width="259" height="286"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To get things running a bit quicker, he persisted all the data for the tests in the database but was still looking to shortcut the endless send/retrieve in &lt;a href="https://www.google.com/aclk?sa=l&amp;amp;ai=DChcSEwi6_unvy7X7AhU2G9QBHcgQAv8YABACGgJvYQ&amp;amp;sig=AOD64_0yX5fsJa30qe6bcriQmtoRokYoHQ&amp;amp;q&amp;amp;adurl&amp;amp;ved=2ahUKEwi0xN7vy7X7AhUjmmoFHU1rBscQ0Qx6BAgJEAE"&gt;Elasticsearch&lt;/a&gt;. So why not &lt;a href="https://redis.io/"&gt;Redis&lt;/a&gt;?  20 lines of code and 1 cached index later, he saw a 20% improvement in runtime on the large test dataset—a  perfect productivity hack! &lt;a href="https://www.youtube.com/watch?v=m-yDCv4CeMU"&gt;Correr, Dora, Correr!&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Show me what you’re working with
&lt;/h2&gt;

&lt;p&gt;The Partner Management &amp;amp; Tools engineering team has the unofficial mission to make life easier for &lt;a href="https://www.lob.com/partner-program"&gt;Lob’s partners&lt;/a&gt; and our internal Partner Operations group; both have expressed the need for greater inventory visibility. Together the team built two new UIs that allow visibility of inventory by partner, and inventory change by day. Previously only available in Datadog; this information is now displayed in an easy-to-digest way; areas of concern can be quickly identified, as well as trends.&lt;/p&gt;

&lt;p&gt;This was a huge win for both our partners and Lob, and for their efforts, the team took home the gold ribbon. Congratulations to Engineering Managers Adam Stodgill and Andrew Jorczak, Senior Software Engineers Martin Han, Tim Bright, and Zac Leids, and Software  Engineers James Cho and Jessica Ho.&lt;/p&gt;

&lt;h2&gt;
  
  
  Filter what you’re working with
&lt;/h2&gt;

&lt;p&gt;Up next, to make a case for “there's no small PRs, only small teams” was solo artist Staff Software Engineer, Landon Barnickle. Our Partner Ops team has also expressed a pain point around filtering. To make their lives easier, Landon added an admin route for managing filtered partners using a wrapper for our &lt;a href="https://www.google.com/aclk?sa=l&amp;amp;ai=DChcSEwiKyNan9bX7AhVRFkwKHSzmCzYYABAAGgJvYQ&amp;amp;sig=AOD64_1aT3n8lL4qoEbxG8G-can5nuFr1g&amp;amp;q&amp;amp;adurl&amp;amp;ved=2ahUKEwiaiMmn9bX7AhWnmGoFHQtGBTcQ0Qx6BAgKEAE"&gt;Redis&lt;/a&gt; cache, or as he put it: List, Add, Remove, WIN! He reminded our engineers of the importance of working closely with the end users; often little changes can make a big impact.&lt;/p&gt;

&lt;h2&gt;
  
  
  See the future, I can
&lt;/h2&gt;

&lt;p&gt;Our Routing and Delivery engineering team (more commonly known by the awesome acronym RAD) also supports our partners. For example, we currently have a tool for our team to create dynamic rulesets around routing to our print partners. To use a very simple example, let’s say we wanted to route all mailpieces from a customer to a specific print partner. Using &lt;a href="https://docs.github.com/en/actions"&gt;GitHub Actions&lt;/a&gt;, our team can create this rule quickly and easily—but they do so in production.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TS4HmmfT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/52l42uy7l7rfdf83ciy9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TS4HmmfT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/52l42uy7l7rfdf83ciy9.png" alt="there is no test only prod (yoda)" width="600" height="597"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The downside of such agility is we don't have a good way of predicting the effect of any given change. Should the print partner in the above example be at capacity, or if there is a conflict with another ruleset (more common), we must make another adjustment.&lt;/p&gt;

&lt;p&gt;Ken Pflum, Staff Engineer, developed a way to allow us to A/B test; in short, we can now work off a particular branch to test rule sets, then merge that branch into main. This predictive ability reduces risk and still allows us to adapt quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  A picture is worth a thousand words
&lt;/h2&gt;

&lt;p&gt;Visibility was a common theme in this year’s hackathon. Lob sends millions upon millions of mailpieces each year which results in a lot of data. For those within the company, there's no easy way to digest that data. Looker dashboards have limitations and even those with SQL chops may run into difficulties writing queries.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--badekhJx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e0ig1aym1hbl92agm2mj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--badekhJx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e0ig1aym1hbl92agm2mj.png" alt="I dont know how to use sql and im afraid to ask" width="300" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Engineers on the RAD team aimed to provide a couple of visualizations depicting mail volume and our routing approach. Why visualizations? They are easier to digest and more descriptive than code and they help surface patterns and anomalies.&lt;/p&gt;

&lt;p&gt;Sample data was used to showcase mail density over the last month across the US:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vlmjxBv3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2uzcybj385qdslk47nyu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vlmjxBv3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2uzcybj385qdslk47nyu.png" alt="mail density" width="880" height="597"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using data from a sample postcard campaign, they also presented a simulation of what a routing profile could look like on the US map. It follows the campaign getting routed through Lob: The bigger bubbles represent Lob’s print partner facilities and the smaller bubbles represent the USPS facilities; the links showcase the USPS facilities where our partners sent our mail.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5EzzR1bZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f2cz6y2zlcxbqilntz0p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5EzzR1bZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f2cz6y2zlcxbqilntz0p.png" alt="routing sample" width="880" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Props to Senior Software Engineer Sachin Muralidhara and Software Engineer Joseph Villanueva, with nods to Staff Software Engineer Landon Branickle and Engineering Manager Adam Stodgill. The team at large is excited to build on this work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The coolest traffic cop
&lt;/h2&gt;

&lt;p&gt;In another effort to standardize development and operations, Lob has just wrapped up our container orchestration &lt;a href="https://www.lob.com/blog/alternative-to-kubernetes"&gt;migration from Convox to HashiCorp’s Nomad&lt;/a&gt;, led by Senior Platform Engineer Elijah Voigt. In this new ecosystem, one feature available to us is &lt;a href="https://developer.hashicorp.com/consul/docs/connect"&gt;Consul Service Mesh&lt;/a&gt; (a feature of &lt;a href="https://www.consul.io/"&gt;Consul&lt;/a&gt;, which is part of our Lob Nomad stack).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With Consul Service Mesh, we can have each of our services declare what their dependencies are, and enforce only traffic from those services.&lt;/strong&gt; So you can tell your container orchestrator: this service should only accept traffic from this other service, and anything else should be blocked (and that service is encrypted). As we scale up, this offers us the same &lt;a href="https://www.datocms-assets.com/2885/1666193737-hashicorp_zts_whitepaper.pdf"&gt;zero trust security&lt;/a&gt; much larger enterprises typically employ.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--T7SqZTol--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y13pum5h0l2670y7g3a9.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--T7SqZTol--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y13pum5h0l2670y7g3a9.gif" alt="dancing traffic cop" width="220" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition to setting us up for a better security posture, Service Mesh also provides a greater understanding of how our services work together. Topologies allow you to quickly identify dependencies and interactions, and details are available for a deep dive.&lt;/p&gt;

&lt;h2&gt;
  
  
  A journey of a thousand miles starts with a single step
&lt;/h2&gt;

&lt;p&gt;Senior Director, of Strategic Business Development, Tyler Dorenburg, and Senior Product Manager, Andrew Reagan are passionate about bringing new functionality to our customers. Working closely with our engineering team they continue to drive innovation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.usps.com/manage/informed-delivery.htm"&gt;Informed Delivery by USPS&lt;/a&gt; is a free daily digest email with digital scans of each mailpiece you will receive each day. Additionally, mailers can add a clickable advertisement underneath each mailpiece scan.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xm48SJUy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bj6vg8fwx7czi4o06jxm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xm48SJUy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bj6vg8fwx7czi4o06jxm.png" alt="informed delivery sample email" width="321" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As of September 1st, USPS adjusted the requirements making it possible for Lob to support this functionality for our customers and their mail campaigns in the near future! The technical details of how we plan to implement this are both mindboggling and proprietary so we won’t share that here, but Staff Software Engineer, Landon Barnickle, and Software Engineer, Gage Guzman, used the hackathon as an opportunity to dive into some of the prework (like digesting a 92-page PDF with XML specs—sounds terrifying!).&lt;/p&gt;

&lt;h2&gt;
  
  
  Go big or go home
&lt;/h2&gt;

&lt;p&gt;Arguably the most frightening moment of the hackathon demos was when Senior Infrastructure Engineer, James Douglas, showed the group our AWS bill. Ek! We are storing over 5 petabytes of data in the form of billions of objects. Our tables have grown so big that we're timing out on a lot of queries.&lt;/p&gt;

&lt;p&gt;We developed a service called “Expunge” which deletes expired data on an ongoing basis but has a hard time working through our current backlog. It’s like using a lawn mower when a feller buncher is what’s needed. (You are not alone if you had to look up “petabyte” and/or “feller buncher.”)&lt;/p&gt;

&lt;p&gt;True to the Lob value &lt;em&gt;Be Bold&lt;/em&gt;, James went in search of the &lt;a href="https://www.iflscience.com/tsar-bomba-the-biggest-nuclear-bomb-ever-detonated-65978"&gt;tsar bomba&lt;/a&gt;, that is, a one-time big-bang approach to clear all the old data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vhqY00Z9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wfkgy6j3axvzopvepdu1.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vhqY00Z9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wfkgy6j3axvzopvepdu1.jpeg" alt="big bang explosion" width="325" height="255"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;He initially explored &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html"&gt;Amazon S3 Lifecycle&lt;/a&gt; which is a built-in wipe but came across a number of limitations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;For example, why not just delete everything that's X days old, right? Wrong. There are special circumstances where we want/need to keep data for longer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What if we tagged them, then have S3 delete the rest? Wrong again; turns out you cannot &lt;em&gt;exclude&lt;/em&gt; a tag in the filters.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Move them to a different storage class? Nope. Can't target only a storage class.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How about we evacuate the files, then copy them back in so it updates the date (to fall within the “keep” period)? Nope. Innocent bystanders would be caught in the slaughter, like templates, which are timeless.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Alas, the tsar bomba approach would be too indiscriminate. Since the goal is to target each S3 individually, James landed on the following instead:&lt;/p&gt;

&lt;p&gt;He attacked the database first by carefully moving everything that qualifies for deletion into a new table and indexing it; this took about 19 hours. From there, you can use Expunge’s core functionality to attack this new table to delete resources. Testing proved this process would take mere minutes; minutes that will save us major moola. This is a huge win for Lob.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;With S3, an ounce of prevention is worth a pound of cure" —Douglas.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Stripe Upgrade
&lt;/h2&gt;

&lt;p&gt;Proactive upgrades are also worth their weight in gold. Software Engineers Sage Farrenholz and Patrick Thulen on our Billing team tackled an upgrade to &lt;a href="https://stripe.com/docs"&gt;Stripe&lt;/a&gt;, the system we use to automate and process payments. True to theme, Sage noted our legacy Stripe integration was pretty “scary”: we were using an older version, and we wanted to move from Charges API to the newer &lt;a href="https://stripe.com/docs/payments/payment-intents"&gt;Payment Intents&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The work was fairly straightforward: They bumped Stripe (Node) Client package to 10.15.0, bumped Stripe API to the latest version 2022-10-0, and moved over to Payment Intents, but this involved multiple breaking changes and an awful lot of testing.&lt;/p&gt;

&lt;p&gt;While not the sexiest of projects (although an accidental dropping of the “e” from “Stripe” on one of the presentation slides would suggest otherwise), this brings tremendous value to our Lob and our customers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;After 48+ hours of non-stop hacking, our Lobsters looked a little more like ghouls. That said, each and every participant, and all those who got to witness the final presentations, had smiles on their faces when all was said and done. Hopefully, they were sincere…&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“No matter the situation, always wear a smile.” –The Joker&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cBjt1PRN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8vwom02y7jyeqmueuiug.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cBjt1PRN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8vwom02y7jyeqmueuiug.jpg" alt="joker" width="880" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Want more?
&lt;/h2&gt;

&lt;p&gt;We love a good theme; read more about our spring hackathon, inspired by a favorite 80's movie: &lt;a href="https://dev.to/lob/hack-to-the-future-a-recap-4imf"&gt;Hack to the Future&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>devops</category>
    </item>
    <item>
      <title>h(API) holidays! Mail your holiday cards programmatically</title>
      <dc:creator>Lob</dc:creator>
      <pubDate>Tue, 06 Dec 2022 17:43:04 +0000</pubDate>
      <link>https://forem.com/lob/mail-your-holiday-cards-programmatically-2hhk</link>
      <guid>https://forem.com/lob/mail-your-holiday-cards-programmatically-2hhk</guid>
      <description>&lt;p&gt;It’s Holiday Card season! But my hand hurts just thinking about addressing all those envelopes, and I can’t remember the last time I bought stamps…let’s be real, ain’t nobody got time for that. &lt;/p&gt;

&lt;p&gt;Fortunately, I happen to work for a company that sends direct mail programmatically. Many developers &lt;a href="https://docs.lob.com/"&gt;integrate with Lob&lt;/a&gt; using one of our SDKs in their preferred programming language, but we recently launched our low-code &lt;a href="https://www.youtube.com/watch?v=ZvST6thqO1I"&gt;Campaigns&lt;/a&gt; and I thought I’d give it a whirl. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The following is a step-by-step tutorial so you too can create personalized full-color 6”x9” postcards (automatically printed and mailed) for under a dollar each.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is a sample of the postcard we will create. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2Q3sM49F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gn91ykuvoorbxg303y56.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2Q3sM49F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gn91ykuvoorbxg303y56.png" alt="postcard front and back" width="880" height="1178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Select and prep your photos
&lt;/h2&gt;

&lt;p&gt;Choose two of your favorite photos: one landscape (for the front), one portrait (for the back). Use your photo editing tool of choice to format each correctly; I used Preview on my Mac. &lt;strong&gt;To ensure that they scale proportionally and print at the highest quality they should be 5x7 inches (2100 pixels x 1500 pixels in landscape or 1500 pixels x 2100 pixels in portrait) and at a resolution of 300 pixels per inch.&lt;/strong&gt; (If you need more support on this part, and/or are snagging photos from your iPhone, see the Appendix.)&lt;/p&gt;

&lt;p&gt;You will then need to host your images somewhere; I used a free account at &lt;a href="https://imgbb.com/"&gt;imgbb.com&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prep your data
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;You will then need to bring up the HTML for the &lt;a href="https://s3.us-west-2.amazonaws.com/public.lob.com/solutions/6x9_holiday_postcard/holiday-front.html"&gt;front&lt;/a&gt; and &lt;a href="https://s3.us-west-2.amazonaws.com/public.lob.com/solutions/6x9_holiday_postcard/holiday-back.html"&gt;back&lt;/a&gt; of the postcard. Click each link and view the HTML; we’ll use it in a later step).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next, you will prep your address data. &lt;a href="https://docs.google.com/spreadsheets/d/1Nlb4qqrP00nmpZRwaSJ9pdoVcmvcXQgbsUKxrmemmPQ/edit?usp=sharing"&gt;&lt;strong&gt;Make a copy&lt;/strong&gt; of this spreadsheet&lt;/a&gt; and input your data:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Do not modify or add any column headers!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add the full name in the &lt;code&gt;name&lt;/code&gt; column&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add the address data (make sure that leading zeroes aren’t being dropped and that the state code is used)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Replace the “Lorem ipsum” in the &lt;code&gt;message&lt;/code&gt; column with your own personalized message. Make sure that the message fits within the space by being shorter than the placeholder examples (about 65 characters) You can test your holiday card messaging &lt;a href="https://s3.us-west-2.amazonaws.com/public.lob.com/solutions/6x9_holiday_postcard/message_test.html"&gt;HERE&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add the links to your front and back images to every row in the &lt;code&gt;image_front&lt;/code&gt; and &lt;code&gt;image_back&lt;/code&gt; columns (should look something like this: &lt;a href="https://i.ibb.co/fpJ84Js/front.png"&gt;https://i.ibb.co/fpJ84Js/front.png&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Download and save your address file as a .csv&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Create your HTML templates
&lt;/h2&gt;

&lt;p&gt;Now you are ready to create your postcard templates in the Lob dashboard. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;First you will need to &lt;a href="https://dashboard.lob.com/"&gt;sign up for a free Lob account&lt;/a&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once you have registered you will be taken to the dashboard. In the Developer tier there is no cost to test out the Print &amp;amp; Mail APIs, but if you want to send live requests—and have a postcard printed and mailed—it will run you $0.896 a pop. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Payment information can be entered &lt;a href="https://dashboard.lob.com/settings/payment"&gt;here&lt;/a&gt;: Your name (top right) -&amp;gt; Settings -&amp;gt; Payment &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create &amp;amp; save HTML templates for the Front and Back of your postcard&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On the left menu bar, Navigate to &lt;a href="https://dashboard.lob.com/templates"&gt;HTML Templates&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make sure Live is highlighted!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click on ‘Create’&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Name this ‘Front Template’ and paste in the HTML code for the front. Click ‘Create’ to save. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On left menu, click HTML Templates to go back to main template screen. (You should see your new Front template there under Live templates.) &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once again, ensuring Live is highlighted, click ‘Create’ again. Name this ‘Back Template’ and paste in the HTML code for the back. Click 'Create' to save. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click HTML templates to go back to main template screen; you should see your new Back template there under Live templates. &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;** Make sure you see both templates are listed when Live is highlighted before proceeding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create and send your postcards
&lt;/h2&gt;

&lt;p&gt;On the left menu bar, navigate to &lt;a href="https://dashboard.lob.com/campaigns/live"&gt;Campaigns&lt;/a&gt;; click on “Create Campaign”&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Configure Campaign
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Enter a Name and Description&lt;/li&gt;
&lt;li&gt;Campaign Type: Marketing&lt;/li&gt;
&lt;li&gt;Mail Type: Postcard; Size: 6x9&lt;/li&gt;
&lt;li&gt;Postage references: Return address is not required; Select postage: Up to you, note the listed expected delivery times&lt;/li&gt;
&lt;li&gt;Cancellation Window: This is set at 5 minutes; you will not have the option to edit on a Developer plan&lt;/li&gt;
&lt;li&gt;Campaign-Level Metadata: Optional&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 2: Add Audience
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Upload your CSV file (created during prep stages), or drag and drop it into the dashboard&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Map required address variable: These are mapped automatically.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 3: Choose Creative
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Under ‘Postcard Front’, select the HTML Template you created from the dropdown menu. (Note a preview will not show your images or messages as they are not mapped yet)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under ‘Postcard Back’, select the HTML Template you created from the dropdown menu. (Note a preview will not show your images or messages as they are not mapped yet)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Connect your creative to your audience: &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Merge variable strictness: Ignore; &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Connect/map your merge Variables from HTML to the appropriate column in the CSV: {img_front} to img_front, { img_back} to img_back, {message} to message&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 4: Review Campaign
&lt;/h3&gt;

&lt;p&gt;If something is not correct, click ‘Previous Step’ to edit. The free Developer tier does not allow you to edit cancellation windows. That said, &lt;strong&gt;once you place your order, after 5 minutes, the live request will be sent to Lob. Your postcards will be created and mailed, and you will be charged.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If all looks correct, click ‘Place Order’ and your holiday cards will be on their way! You can view your cards under Campaigns in the Lob dashboard. &lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;Lob was started by two developers who needed to solve a problem, and &lt;a href="https://docs.lob.com/"&gt;direct mail APIs&lt;/a&gt; were the answer; the goal was to make sending mail just as easy as sending an email. This is a fun personal use case, but Lob is meant to automate direct mail—postcards, letters, checks, self-mailers, and more—at scale. Just like email, each mailpiece can be highly personalized, and for advanced tiers, Mail Analytics is available for each mailpiece to track each postcard along its journey to delivery (and if you included a QR code, you could even track open rate). Check out &lt;a href="https://www.lob.com/"&gt;Lob.com&lt;/a&gt; if you want to learn more. &lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix: How to prep a photo
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Email/Download a photo from your iPhone onto your computer. The following steps are shown in the Preview app (Mac) but are a good guideline for any editing program. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select your back photo (portrait orientation). Under &lt;strong&gt;Tools&lt;/strong&gt;, select &lt;strong&gt;Adjust Size&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pCXOmsFm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/roceinryw45zigzsbns2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pCXOmsFm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/roceinryw45zigzsbns2.png" alt="adjust size" width="880" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Change Resolution to 300 pixels/inch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For this photo (portrait orientation): Change Width to 5 inches; and Height will automatically change. Alternatively, update the Height to 7 inches, and the Width will change. &lt;strong&gt;It’s highly likely you won’t end up with a perfect 5x7; that’s ok: Change the field that results in one of the measurements being slightly too big&lt;/strong&gt;; you will crop in the next step).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GhzSIVJj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/za8o2omur8szn0bo76n0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GhzSIVJj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/za8o2omur8szn0bo76n0.png" alt="adjust size and resolution" width="880" height="806"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Under &lt;strong&gt;Edit&lt;/strong&gt;, &lt;strong&gt;Select All.&lt;/strong&gt; Drag the dotted line to crop the image to the necessary 1500x2100 (as you move the line, the measurements will display; exaggerated below).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tkBDFvGs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tjjhmfi2bigetlkc1tt9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tkBDFvGs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tjjhmfi2bigetlkc1tt9.png" alt="cropping image" width="432" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Under &lt;strong&gt;Tools&lt;/strong&gt;, &lt;strong&gt;Assign Profile&lt;/strong&gt; select Generic RGB. (Photos must be saved as RGB.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make sure your photo is named clearly as the BACK. Save!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Repeat these steps for **the FRONT photo, but in landscape orientation (7” wide x 5” high, 2100 pixels x 1500 pixels).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>tutorial</category>
      <category>programming</category>
      <category>api</category>
      <category>html</category>
    </item>
    <item>
      <title>Tips for starting a new job as a Staff+ Software Engineer</title>
      <dc:creator>Lob</dc:creator>
      <pubDate>Thu, 01 Dec 2022 17:18:56 +0000</pubDate>
      <link>https://forem.com/lob/tips-for-starting-a-new-job-as-a-staff-software-engineer-45pd</link>
      <guid>https://forem.com/lob/tips-for-starting-a-new-job-as-a-staff-software-engineer-45pd</guid>
      <description>&lt;p&gt;My experience getting hired at Lob at the Staff level made me realize I may have some learnings to share. I've been at the Staff level previously, but that was at a company where I had been promoted up to those levels; I was already very familiar with my team, my company, our business, and our platform, so it was just a natural progression of responsibility for me. But getting my current job was the first time I'd started at a new company at such a level of seniority. And while at least for me, &lt;strong&gt;every new job is a major invitation for an Imposter Syndrome attack, this had the potential to be an even greater one. But I fought it, and I was really intentional about how I approached getting up to speed&lt;/strong&gt; and be effective more quickly and smoothly than I have in previous new job experiences. &lt;br&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpuqcccn64hjp9x039rxh.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpuqcccn64hjp9x039rxh.jpeg" alt="baby yoda looking up" width="600" height="624"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  So you've landed the job, now what?
&lt;/h2&gt;

&lt;p&gt;You worked hard to get through that interview cycle, you got the job offer, you accepted it, —you won! But then the insidious thoughts can start. "But what if they got it wrong and I'm actually not good enough? What if people there are smarter than me? What if they don't like me? What if their expectations of Staff are different than where I was before and I don't meet them?" The stories can go on and on if you let them.&lt;/p&gt;

&lt;p&gt;This is where I stop the stories and I tell myself, "Hi, Imposter Syndrome. I see you. But I'm not going to let you control my life. Even though I'm feeling really nervous right now, here are some facts I can tell myself:”&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;I got the job. They liked me enough, they saw enough of something in me to offer me the job.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I have a proven track record for performing, getting positive feedback and reviews, and getting promoted. All of those people saw something in me and they can't all be wrong.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;All I can ever do is my best. I can work hard, I can try my best, and if that's not good enough, then that's outside of my control. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Worrying about what might be before I have any information to act on isn't going to change or help anything.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There will be a number of people that are smarter than me or have more experience than I do in a number of things, and that's okay. If I'm the smartest person someplace, then who will I learn from? How will I grow? &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fweyehtd98owl7o03douo.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fweyehtd98owl7o03douo.jpeg" alt="stuart smalley" width="720" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  No one is a know-it-all
&lt;/h2&gt;

&lt;p&gt;Let’s address that last point. Starting at a new company, at a level of some seniority, it can feel like you should be expected to be the expert in all the things. Well, there's this concept that I constantly remind myself of called being T-shaped; inspired by this article in Forbes: &lt;a href="https://www.forbes.com/sites/lisabodell/2020/08/28/futurethink-forecasts-t-shaped-teams-are-the-future-of-work/?sh=3212c7385fde"&gt;Why T-shaped teams are the future of work&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;While generalists know a &lt;em&gt;little&lt;/em&gt; about a &lt;em&gt;lot&lt;/em&gt; of subjects, and I-shaped employees are experts in a &lt;em&gt;single&lt;/em&gt; area, &lt;strong&gt;a T-shaped person is a subject-matter expert in at least one area &lt;em&gt;and&lt;/em&gt; knowledgeable or skilled in several others.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If we lined up several t-shaped engineers in an organization we would have plenty of overlap in general knowledge but bring a different mix of experience and specialization to the table. So, I don’t have to be the expert in everything; we can share the load.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frwpkjg8dt2ky92hlpb6z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frwpkjg8dt2ky92hlpb6z.png" alt="Ideal team of tshaped employees" width="800" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the most powerful blog posts I've read, and it really helped me personally with this concept was written by a well-known engineer I respect, Dan Abramov, titled &lt;a href="https://overreacted.io/things-i-dont-know-as-of-2018/"&gt;The Things I Don't Know&lt;/a&gt;. He very openly and publicly shared with thousands of followers that while he may be an expert in a number of things, there are plenty of things that he doesn't know very well or at all. What was really empowering about that list is there were some things that he didn't know that I did. So if Dan Abramov can be brilliant at some things and struggle with others, I can too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Behaviors to Avoid
&lt;/h2&gt;

&lt;p&gt;You’re really going to want to show everyone as soon as possible that you know what you’re doing and deserve the title you’ve been given.  But, here are some things to watch out for so you don’t end up starting off on the wrong foot and acting like a jerk.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Don’t assert things just to show off what you know
&lt;/h3&gt;

&lt;p&gt;It's fine and absolutely appropriate for you to be sharing knowledge and helping to educate others, but just make sure the context is right. **If you find yourself constantly feeling the urge to talk about the things you've done before or point out something you know that doesn't actually add value to the conversation, try to hold back. **This can make you either sound arrogant or desperate if you do it too much. You will have plenty of opportunities to demonstrate your skills and knowledge. &lt;/p&gt;

&lt;h3&gt;
  
  
  2. Don’t try to change too much too soon—take some time to observe and learn first
&lt;/h3&gt;

&lt;p&gt;Maybe something isn't exactly the way you would've done it or have done it in the past, but that doesn't necessarily mean it's wrong. Make note of these things that seem to go against what you would've done in the past, but resist the urge to jump in immediately and call it wrong. Part of your role is to improve things, and teach others how to do things in a better way, but until you've had time to really get your surroundings, you may not have enough context yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Take some time to get the full picture.&lt;/strong&gt; Gather data, ask questions, but withhold judgment until you've had a little more time to get to know people, processes, and history. Ask questions humbly and from a place of curiosity.&lt;/p&gt;

&lt;p&gt;Along that note, pick your battles. When you have that instinct to assert that something should be done differently, continue to ask yourself, “How important is this right now?” Make a note, and you can revisit it later with more context. &lt;/p&gt;

&lt;h3&gt;
  
  
  3. Resist perfectionism
&lt;/h3&gt;

&lt;p&gt;During this time of high insecurity, you're going to want to project nothing but perfection. &lt;strong&gt;But perfectionism is a myth and thinking that you have to be perfect is toxic, not just for you but for the others around you. Part of being a leader is mentoring others, and a portion of that is done by what behavior we are modeling to them.&lt;/strong&gt; If we show others that we never make mistakes, there's nothing we don't know, we work however many hours it takes to get the job done, we're always available, we're never stressed or tired—I could go on and on—we send that message to others that in order for them to be successful, they have to be that way, too.&lt;/p&gt;

&lt;p&gt;It encourages them to set unhealthy, unfair, and unrealistic expectations of themselves. So as much as it goes against that urge to prove yourself to everyone right now, you are doing a greater service to others if you can model some vulnerability. More junior folks need to see that everyone makes mistakes, no matter how much experience you have. &lt;/p&gt;

&lt;p&gt;What_ is_ important is demonstrating how you handle yourself when you make a mistake. Calmly own your mistake openly and early. "Here's what happened and here's my game plan for what I'm going to do about it." Take responsibility without being embarrassed or overly apologetic. Just be transparent about it and then get it fixed. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdfai3lyqxw4a6y5wo34j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdfai3lyqxw4a6y5wo34j.png" alt="made a huge mistake" width="540" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We should foster an environment of continual learning. There were many times when I'd have to say, I don't know how to do that yet, but I'll get back to you. Then I'd use that as an opportunity to go figure that thing out.  Don't act like you're just so smart that nothing is hard for you, but rather show others it doesn't have to be scary to do something you don't know how to do. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;As software engineers, we need to be comfortable with being uncomfortable&lt;/strong&gt; and able to figure things out. I see a lot more junior folks trying to stay in the areas they're already familiar with and avoid branching out. They're hanging out in the shallow end of the pool. Show them by example that swimming into the deep end doesn’t have to be intimidating. &lt;/p&gt;

&lt;h2&gt;
  
  
  Behaviors to Adopt
&lt;/h2&gt;

&lt;p&gt;Now let’s address what you should do to get up to speed quickly and start providing value ASAP.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Start taking notes early
&lt;/h3&gt;

&lt;p&gt;Your role as a Staff + Software Engineer is to be a sharpened tool. You bring with you a lot of experience and skills and you can be deployed to accomplish tasks for your team, your manager, or even the engineering organization level. You may be given the responsibility and freedom to work on special projects or tasks that cross team boundaries. You may be given a level of autonomy to decide or at least propose what you think you should be working on. &lt;/p&gt;

&lt;p&gt;That said, you can only do so many things at a time. So you have to be very thoughtful about how you prioritize your time and what you choose to spend that time on (more on that in a moment).  But as you're learning your way around, start taking notes. You should be keeping your eyes out for opportunities to improve, optimize, and make systems, processes, and people better, but early on, just make notes on all of those things you notice for reference later.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Prioritize what you're spending that time on
&lt;/h3&gt;

&lt;p&gt;As you're finding yourself getting caught up on the learning curve, and you're starting to feel more comfortable with your surroundings, that's a good time to start reviewing that list of items you've been building. Is everything on it still something worth spending time on? Can/should any of those things be delegated to others? Whatever's left will need prioritizing.&lt;/p&gt;

&lt;p&gt;In the book _&lt;a href="https://staffeng.com/book"&gt;Staff Engineer&lt;/a&gt; _by Will Larson (which I highly recommend) he references a quadrant of impact versus effort.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu3vpdj1nf6iujnd8t0gz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu3vpdj1nf6iujnd8t0gz.png" alt="quadrant snapshot" width="800" height="693"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;“&lt;a href="https://staffeng.com/guides/work-on-what-matters"&gt;Work on What Matters&lt;/a&gt;” is worth a read to understand where you might go astray, but, in short, &lt;strong&gt;the goal is to aim for the top half of the quadrant at the “high impact” tasks.&lt;/strong&gt; Keep checking in with yourself, both before you prioritize your work, and after you’ve started a task, to make sure it’s the right thing at that time for you to be doing. &lt;/p&gt;

&lt;h3&gt;
  
  
  3. Start meeting people
&lt;/h3&gt;

&lt;p&gt;If you're an introvert or you have some social anxiety this could be really difficult. But  I've learned how to push through that anxiety when it's important. Inside, I may be wanting to curl up in a ball and hide, but outside I try to project comfort and confidence; I think that's a crucial skill for being successful as a software engineer. &lt;/p&gt;

&lt;p&gt;I can't take credit for this next idea, but now that I've done it, I think it's a must-do when starting a new job. At Lob, every new hire is given a list of people by their manager whom in their first two or three weeks, they have to set up one-on-one meetings with. These are people that are going to be important for the new hire to get to know in their role, or who can provide context or history about the company and business. These can be in-person or occur remotely. (If remote, I would really encourage you to turn your camera on. I think that it is so important to see people's faces and to really feel like they're in that room with you to build that familiarity and rapport).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcuyxoxr78m19d01m20yt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcuyxoxr78m19d01m20yt.png" alt="what would you say you do here" width="500" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was an invaluable exercise for me; it provided me with context on the decisions and directions that had been made before me, and helped me quickly develop relationships and build my network of peers and go-to people for the various things I would need in my job. &lt;/p&gt;

&lt;p&gt;Try to distill from others that you talk to what are the biggest problems, pain points, and challenges that they think the organization is facing today? These are good things to take note of so that as you settle into your role, you could try to find ways to improve on these things.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Learn in public
&lt;/h3&gt;

&lt;p&gt;While you're getting up to speed on so many things, it's a perfect time to start a “learn in public” culture at your company if it doesn't already have one. Learning in public is where you share what you've learned as you're learning it. &lt;/p&gt;

&lt;p&gt;We don't have to hide while spending all night trying to cram on some framework we don't know just so we can log on tomorrow and act like we've been an expert all along. We can be open about where we're at today, and at the same time, we can share the learnings so that others can benefit from what we've discovered. &lt;strong&gt;Ask questions in public channels and keep knowledge sharing out of DMs. Show others that they don't have to be embarrassed to ask questions.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;In turn, when you learn something new, share it publicly and share often. At Lob, we have a Today-I-Learned channel in Slack, where anyone can share anything in there and the whole engineering organization can see it.&lt;/p&gt;

&lt;p&gt;Another great way to share learnings is to demonstrate them. If you've learned how to do something or if someone asks you to show them how to do something, instead of just showing them privately, consider recording a video. Now you can share that video and more people can benefit from that demonstration. &lt;/p&gt;

&lt;p&gt;With all of this, try to keep the barrier low. Don't spend a ton of time putting together a perfect dissertation or a superbly polished video. Just hit the record button and start talking. The more you do this, the more others will catch on and not feel intimidated to do it themselves too. &lt;/p&gt;

&lt;h3&gt;
  
  
  5. Know your learning styles; use them to get to know the landscape more quickly
&lt;/h3&gt;

&lt;p&gt;If you aren’t familiar with learning styles and what yours are, a simple Google search can educate you as well as lead you to a number of quizzes to pinpoint what works best for you for learning and retaining knowledge. &lt;strong&gt;Once you know what styles work well for you and which really don't, you should lean on that awareness to really focus your plan for how to get up to speed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That being said, you should probably do at least a little of each approach, because we shouldn't limit ourselves to just our preferred methods. We need to be flexible and perhaps with practice, styles that haven't been working well for us can be improved.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmuw9gbspo442qcoy1x72.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmuw9gbspo442qcoy1x72.png" alt="learning style icons" width="800" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Visual or Spatial learners&lt;/strong&gt; do best when looking at visual or spatial representations of information. This could include things such as charts, diagrams, images, and anything that shows ideas in an illustrated way. You may want to lean on using UML diagrams to help you learn more about the platform, interactions between applications, and even within applications. Build your own integration and activity diagrams, or even just flow charts as you trace through things. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Auditory learners&lt;/strong&gt; do best when hearing information presented to them. Reach out to those people you've been meeting in that network or peer group you've been building and ask them to walk you through some things. Ask people to pair program with you, either on your coding tasks or theirs. (This can be a gift to more junior folks because it gives them an opportunity to teach and explain something that they know to someone else!)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Verbal learner&lt;/strong&gt;s do better when reading and can retain that information better when they write or speak about it. So read the docs! As you learn more, start writing and contributing to those docs. Read code, especially PRs, even if you're not the requested reviewer. This can help you see firsthand where things are being done and how. This can be a great way for verbal learners to quickly pick up languages, frameworks, and libraries they're not familiar with yet by seeing exactly how those technologies are being used. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The kinesthetic learning&lt;/strong&gt; style is learning by doing, and I've lumped this together with the &lt;strong&gt;logical learning style&lt;/strong&gt;, which involves the use of logical reasoning when processing data and solving problems. In software engineering, I think these two often go together. It may seem like the chicken or the egg situation. If you're trying to learn and get up to speed, but the best way you learn is by doing and solving problems, how can you do things if you still don't know enough? Sometimes you just have to jump into the deep end and be over your head for a little bit. So find projects or tickets to work on that force you to learn your way around. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Last but not least
&lt;/h2&gt;

&lt;p&gt;Try to have fun! Hopefully through all of this, you can keep that Imposter Syndrome monster at bay and really believe that &lt;strong&gt;you are exactly where you're supposed to be—and deserve to be&lt;/strong&gt;, and you can just enjoy the process. Enjoy meeting your new teammates and forging new relationships. Get to know your manager and let them be an ally for you. Build that peer group so you have other people you can confide in and share with if you're feeling stressed, frustrated, or overwhelmed. And celebrate and be proud of your accomplishments—you worked hard to get here and you should feel good about it!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1l4315kvk1ljy1c80a0h.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1l4315kvk1ljy1c80a0h.jpeg" alt="new job swagger" width="500" height="746"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Content adapted from Lob Staff Engineer, Erin Doyle’s, presentation at REFACTR.TECH: &lt;a href="https://www.youtube.com/watch?v=PKm39Bsc5rk"&gt;How to Hit the Ground Running Starting as a Staff Software Engineer at a New Company&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>leadership</category>
      <category>wecoded</category>
      <category>womenintech</category>
    </item>
    <item>
      <title>The Definitive Guide to Node.js Backend Development</title>
      <dc:creator>Lob</dc:creator>
      <pubDate>Tue, 15 Nov 2022 16:41:29 +0000</pubDate>
      <link>https://forem.com/lob/the-definitive-guide-to-nodejs-backend-development-3ec9</link>
      <guid>https://forem.com/lob/the-definitive-guide-to-nodejs-backend-development-3ec9</guid>
      <description>&lt;p&gt;&lt;em&gt;I was poking around our engineering blog when I came across this post from 2021, highlighting the release of this book. Though no longer employed by Lob, I hope you'll agree it's still a fantastic resource from Thomas Hunter II worth sharing!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Node.js has grown at such an incredible rate that the reference materials have struggled to keep up at times. Now Node developers finally have a complete source for learning to build robust applications, thanks to former Lob Staff Engineer, Thomas Hunter II.&lt;/p&gt;

&lt;p&gt;Hunter’s new book, &lt;a href="https://www.oreilly.com/library/view/distributed-systems-with/9781492077282/"&gt;Distributed Systems with Node.js: Building Enterprise-Ready Backend Services&lt;/a&gt; is a hands-on guide to help intermediate and advanced developers build their skills, and learn to harness the incredible capabilities of Node.js in a production environment. &lt;/p&gt;

&lt;p&gt;The book is an indispensable tool for intermediate and advanced developers looking to build the backend skills they need to run Node.js in an enterprise production environment. Developers will learn everything they need to know to build, deploy and scale robust applications, integrate and communicate with other services, monitor application health and above all, ensure reliability in the apps they build.&lt;/p&gt;

&lt;p&gt;While there are plenty of resources for frontend Node developers, the book has filled a much-needed gap in backend instruction, earning praise from developers, execs and entrepreneurs. &lt;/p&gt;

&lt;p&gt;Mixmax CTO Brad Vogel has called the book, “The missing manual for scaling Node applications,” and made it required reading for engineers at the company. &lt;/p&gt;

&lt;p&gt;It’s also an important milestone in the rapid growth of Node.js from an innovative environment for startups into a crucial, enterprise-grade tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Growth of Node.js
&lt;/h3&gt;

&lt;p&gt;From its creation by Ryan Dahl in 2009, Node.js has driven innovation, with features and paradigms absent from industry mainstays like PHP. Node quickly solved the C10k problem — the challenge of optimizing a server to concurrently handle 10,000 connections — a significant challenge for developers at the time. &lt;/p&gt;

&lt;p&gt;Innovative characteristics like its event loop design pattern and asynchronous operations quickly began to shake things up, from the startup world to some of the world’s biggest digital enterprises. LinkedIn launched an overhauled mobile app that works with an API written on Node,js in 2011, just two years after the initial release. In the years since, it has been harnessed for the ecommerce backends of eBay and AliExpress, and helped power many of the world's busiest websites like Netflix, Groupon and PayPal.&lt;/p&gt;

&lt;p&gt;While originally designed with the needs of servers in mind, Node.js has also found a home on the desktop, powering Microsoft Visual Studio Code, NVIDIA drivers, and many other important applications. &lt;/p&gt;

&lt;p&gt;As an early adopter, Hunter grew alongside the platform, garnering knowledge and experience that the next generation of Node.js developers will find invaluable.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Agility of Node.js
&lt;/h3&gt;

&lt;p&gt;While many tech leaders use Node to power their servers, Hunter points out that its most enthusiastic adopters have been small, agile companies. Many large enterprises already have substantial resources invested in more traditional programming paradigms and are committed to them, as Hunter has experienced first-hand.&lt;/p&gt;

&lt;p&gt;Before he came to Lob, Hunter worked for a startup that built a security tool for Node.js-based applications. However, when it was acquired by a major company that was committed to Java, he felt less than enthusiastic about the slow, plodding approach to development typical of large, traditional enterprises. He left for Lob, a company that has built with Node from the beginning, and upholds an agile development philosophy that matched his own.&lt;/p&gt;

&lt;p&gt;Why does he prefer Node? One reason is the speed and flexibility of the language. &lt;/p&gt;

&lt;p&gt;“It’s so quick to prototype or get something running,” says Hunter. “Turnaround is so fast. You can innovate in ways competitors can’t.”&lt;/p&gt;

&lt;p&gt;That speed has enabled Lob developers to maintain an ambitious release schedule, often deploying changes ten times per day. That kind of schedule isn’t generally possible with Java apps, which can take hours just to compile, often limiting companies to quarterly deployments.&lt;/p&gt;

&lt;p&gt;Node’s asynchronous operation is also a great fit for Lob’s services in particular, and an engaging challenge for Hunter. The software can proactively send notifications at each stage of a process, such as creating, printing and delivering a postcard, without waiting for a client to request a status update from Lob. This keeps customers continuously appraised of progress and in control of the process, while minimizing resource usage. &lt;/p&gt;

&lt;p&gt;Hunter is also a big fan of Node’s open-source identity, enabling anyone to contribute instead of giving one company control of the platform. That has enabled Node to benefit from its huge user community. Node.js users have built the world’s largest package library, with prebuilt packages available for anything from obscure databases to new protocols.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Book Six Years in the Making
&lt;/h3&gt;

&lt;p&gt;Hunter was initially inspired to take up programming as a teenage game developer, first starting with languages like Perl and PHP. When he later discovered Node.js he was excited by its applications for building multiplayer games where players could interact with other players in near real time. However, it took him longer to learn more niche Node applications, such as standing up databases and synchronizing processes. While the tools were there, the educational resources hadn’t been fleshed out, making it difficult for new developers to learn backend skills. &lt;/p&gt;

&lt;p&gt;Hunter points out that this experience is common for application developers, who often get thrown into situations with use cases that they aren’t familiar with, like load balancing or communicating with another server. So as he worked for companies as a Node.js developer over the past six years, Hunter gained knowledge applicable to the book, eventually building a resource to help other developers thrive in both enterprise and startup roles. &lt;/p&gt;

&lt;p&gt;By teaching Node.js developers advanced skills, Hunter also hopes to encourage developers and CTOs to make their applications more robust and scalable.&lt;/p&gt;

&lt;p&gt;“One of my hopes is that a CTO or early employee at a startup might realize they should dedicate resources to system observability early on in the process.” &lt;/p&gt;

&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;p&gt;The book is available &lt;a href="https://shop.aer.io/oreilly/p/distributed-systems-with/9781492077299-9149"&gt;directly from the publisher&lt;/a&gt;, O’Reilly, as well as from &lt;a href="https://www.amazon.com/Distributed-Systems-Node-js-Building-Enterprise-Ready/dp/1492077291"&gt;Amazon&lt;/a&gt; and other book vendors.&lt;/p&gt;

&lt;p&gt;You can learn more about Hunter, books he’s published, tech talks, and more &lt;a href="https://thomashunter.name/"&gt;on his blog&lt;/a&gt;. &lt;/p&gt;

</description>
      <category>node</category>
      <category>tutorial</category>
      <category>programming</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Stop Wasting Connections, Use HTTP Keep-Alive</title>
      <dc:creator>Lob</dc:creator>
      <pubDate>Fri, 04 Nov 2022 16:28:08 +0000</pubDate>
      <link>https://forem.com/lob/stop-wasting-connections-use-http-keep-alive-2iip</link>
      <guid>https://forem.com/lob/stop-wasting-connections-use-http-keep-alive-2iip</guid>
      <description>&lt;p&gt;With the proliferation of third-party APIs and microservice architectures, modern web servers can make as many outgoing HTTP requests as the number of incoming HTTP requests they serve. A typical web application can interact with third-party APIs to handle payment processing, send email, track analytics, dispatch text messages, &lt;a href="https://lob.com/products/address-verification"&gt;verify mailing addresses&lt;/a&gt;, or &lt;a href="https://lob.com/products/print-mail/postcards"&gt;even deliver physical mail&lt;/a&gt;. A server can also rely on internal APIs to fetch account information, start asynchronous processes, or perform complex searches. Programs that initiate a high volume of outgoing HTTP requests must minimize the overhead of each in order to remain performant and optimize resource utilization.&lt;/p&gt;

&lt;p&gt;One of the best ways to minimize HTTP overhead is to reuse connections with &lt;a href="https://en.wikipedia.org/wiki/HTTP_persistent_connection"&gt;HTTP Keep-Alive&lt;/a&gt;. This feature is commonly enabled by default for many HTTP clients. These clients will maintain a pool of connections—each connection initializes once and handles multiple requests until the connection is closed. Reusing a connection avoids the overhead of making a DNS lookup, establishing a connection, and performing an SSL handshake. However, not all HTTP clients, including the default client of Node.js, enable HTTP Keep-Alive.&lt;/p&gt;

&lt;p&gt;One of Lob's backend services is heavily dependent on internal and external APIs to verify addresses, dispatch webhooks, start AWS Lambda executions, and more. This Node.js server has a handful of endpoints that make several outgoing HTTP requests per incoming request. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enabling connection reuse for these outgoing requests led to a 50% increase in maximum inbound request throughput, significantly reduced CPU usage, and lowered response latencies. It also eliminated sporadic DNS lookup errors.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Benefits of HTTP Keep-Alive
&lt;/h2&gt;

&lt;p&gt;Running a benchmark validates the performance benefits of HTTP Keep-alive. The following chart displays the total time taken to make 1000 GET requests for both non-reused and reused connections with a varying number of requests made concurrently. It shows that for all levels of tested concurrency, reusing connections reduces the total run time by a factor of roughly 3.&lt;/p&gt;

&lt;p&gt;Another observed benefit of reusing HTTP connections is reduced CPU utilization. On Mac OS X, this reduction manifests in the Node process itself and in a process named mDNSResponder, an operating system service responsible for resolving DNS. Running top -stats pid,command,cpu | grep -E "(mDNSResponder|node)\s" during both benchmarks shows the contrast in CPU usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Without Connection Reuse
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GoAs37NE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1667577332426/LlRrIDi7D.png%2520align%3D%2522left%2522" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GoAs37NE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1667577332426/LlRrIDi7D.png%2520align%3D%2522left%2522" alt="WITHOUTconnection_reuse.png" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  With Connection Reuse
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wszDDonQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1667577341426/FXLFUw8BU.png%2520align%3D%2522left%2522" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wszDDonQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1667577341426/FXLFUw8BU.png%2520align%3D%2522left%2522" alt="WITHconnection-reuse.png" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inspecting the &lt;a href="http://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html"&gt;flamegraph&lt;/a&gt; of the benchmark script without connection reuse reveals the reason for increased CPU utilization in Node. A large percentage of CPU time is spent on establishing connections and performing SSL handshakes. For example, the flame fragment below shows that 14% of measured CPU ticks occurred while creating a socket.&lt;/p&gt;

&lt;p&gt;It should be noted that initiating connections also incurs overhead for HTTP servers. Therefore, reusing connections also reduces overhead for servers handling these requests.&lt;/p&gt;

&lt;p&gt;Flamegraphs of each benchmark are available to explore: &lt;a href="https://mgartner.github.io/node-keep-alive-benchmark/keep-alive-off.html"&gt;flamegraph without connection reuse&lt;/a&gt;, &lt;a href="https://mgartner.github.io/node-keep-alive-benchmark/keep-alive-on.html"&gt;flamegraph with connection reuse&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The benchmarking scripts are documented in &lt;a href="https://github.com/mgartner/node-keep-alive-benchmark"&gt;node-keep-alive-benchmark&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reduced DNS Errors
&lt;/h2&gt;

&lt;p&gt;Reusing connections also eliminated a set of DNS errors that occurred sporadically within our service. When connections are not reused, a new connection is initialized for each outgoing request. In Node, this initialization includes a DNS lookup to determine the IP of the domain to send the request to. A high volume of DNS lookups can lead to sporadic errors of the form Error: getaddrinfo ENOTFOUND.&lt;/p&gt;

&lt;p&gt;Based on several issues in the Node repository (&lt;a href="https://github.com/nodejs/node-v0.x-archive/issues/7729"&gt;nodejs/node-v0.x-archive#7729&lt;/a&gt;, &lt;a href="https://github.com/nodejs/node-v0.x-archive/issues/5488"&gt;nodejs/node-v0.x-archive#5488&lt;/a&gt;, &lt;a href="https://github.com/nodejs/node/issues/5436"&gt;nodejs/node#5436&lt;/a&gt;) this error can occur when a DNS server fail to respond, perhaps due to it rate-limiting requests. Reducing DNS lookups can reduce or eliminate these errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips when Reusing Connections
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Check Your Timeouts
&lt;/h3&gt;

&lt;p&gt;In some cases, reusing connections can lead to hard-to-debug issues. Problems can arise when a client assumes that a connection is alive and well, only to discover that, upon sending a request, the server has terminated the connection. In Node, this problem surfaces as an Error: socket hang up.&lt;/p&gt;

&lt;p&gt;To mitigate this, check the idle socket timeouts of both the client and the server. This value represents how long a connection will be kept alive when no data is sent or received. Make sure that the idle socket timeout of the client is shorter than that of the server. This should ensure that the client closes a connection before the server, preventing the client from sending a request down an unknowingly dead connection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't Use Node's Default HTTP Agents
&lt;/h3&gt;

&lt;p&gt;For Node services, the &lt;a href="https://github.com/node-modules/agentkeepalive"&gt;agentkeepalive&lt;/a&gt; library provides HTTP and HTTPS agents that enable connection reuse by default. These agents also have other sensible defaults that the standard libraries agents do not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;Connection reuse should provide significant performance improvements to services written in any language that are making numerous outgoing HTTP requests. Some HTTP clients enable this behavior by default, but not all. Some widely used languages and libraries do not enable HTTP Keep-Alive by default, such as Node, so be sure to check the documentation and source code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Keep-Alive"&gt;More on HTTP Keep-Alive from Mozilla&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>http</category>
      <category>performance</category>
      <category>api</category>
    </item>
    <item>
      <title>Predicting deliverability with confidence</title>
      <dc:creator>Lob</dc:creator>
      <pubDate>Wed, 12 Oct 2022 17:48:10 +0000</pubDate>
      <link>https://forem.com/lob/predicting-deliverability-with-confidence-3f7n</link>
      <guid>https://forem.com/lob/predicting-deliverability-with-confidence-3f7n</guid>
      <description>&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; Win trivia night with more than you may ever need to know about USPS deliverability data…but when that data is key to driving decisions, you’ll be glad you went on the following ride with us.&lt;/p&gt;

&lt;p&gt;Our customers send millions of mailpieces through our direct mail automation software every single day, and to do so requires a great deal of trust. But what happens when we lose confidence in the data driving our product? (Spoiler alert: We level up.)&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;del&gt;Houston&lt;/del&gt; Lob, we have a problem
&lt;/h2&gt;

&lt;p&gt;Lob has a large indirect marketing customer that sends direct mail campaigns via our Print &amp;amp; Mail APIs. They own their own address data but verify that data against our &lt;a href="https://www.lob.com/address-verification"&gt;Address Verification API&lt;/a&gt;; month-over-month they have about a 2.2% return to sender (RTS) across all campaigns. But a few months ago, they reported individual campaigns returning as much as 97% of mail submitted back to sender. Eek.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7PPyI6WH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yo4exh5496tdtbusvb2y.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7PPyI6WH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yo4exh5496tdtbusvb2y.gif" alt="panic" width="350" height="196"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;They sent us pictures of piles of postcards with the dreaded “nixie” label (Return to Sender) slapped on each. Mail can be RTS for a variety of reasons, some common examples are &lt;em&gt;insufficient address&lt;/em&gt;, &lt;em&gt;unable to forward&lt;/em&gt;, or &lt;em&gt;vacant&lt;/em&gt;, but we saw the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ydmXsTrc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/forjzzk3jk0begf73f6m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ydmXsTrc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/forjzzk3jk0begf73f6m.png" alt="nixie label" width="416" height="121"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As the verbiage would indicate, this means there was no mailbox or USPS-approved container to receive mail.&lt;/p&gt;

&lt;p&gt;This customer facilitates direct mail campaigns for their customers by allowing them to geographically select an area to send mail to. Cases in which a user may be attempting to send to a rural area, a specific complex that rejects marketing mail, or perhaps entire neighborhoods without mail receptacles may be partially responsible for the high volume of RTS, but this answer is insufficient as it leaves room for doubt in the quality of our AV product. The confidence in our solution becomes dubious if there are numerous failed campaigns and irritated end-users, regardless of reasoning. A boost in efficacy in our product became critical at this juncture, and one solution arose from re-evaluating the finer data points of our Address Verification API. &lt;/p&gt;

&lt;h2&gt;
  
  
  Digging into the data
&lt;/h2&gt;

&lt;p&gt;The thing is, the data we had led us to believe these were clean addresses. Our API response would have looked something &lt;a href="https://docs.lob.com/#tag/US-Verifications/operation/us_verification"&gt;like this example in our API documentation&lt;/a&gt;. Notable fields include ”deliverability”, “valid_address”, "deliverability_analysis", and “lob_confidence_score.”&lt;/p&gt;

&lt;p&gt;Lob’s Address Verification product is &lt;a href="https://www.lob.com/blog/what-is-cass-certification"&gt;CASS certified&lt;/a&gt;, and we include USPS deliverability prediction in the API response. Upon investigation, &lt;strong&gt;these postcards were returned to sender despite USPS confidence they would be “deliverable.”&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;The “valid_address” field indicates whether an address was found in a more comprehensive address dataset that includes sources from the USPS, open source mapping data, and our proprietary mail delivery data. &lt;strong&gt;These postcards were being returned despite a value of “true.”&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Speaking of our proprietary data, in 2020, our team developed the &lt;a href="https://www.lob.com/blog/lob-confidence-score"&gt;Lob Confidence Score&lt;/a&gt;. Because we process addresses and ingest USPS tracking events from the mailpieces we send, we have an enormous amount of data we can marry together to calculate a Confidence Score that predicts the likelihood of delivery. But &lt;strong&gt;these postcards were returned to sender despite Lob having 100% confidence they would be deliverable.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--l-vYeASD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g16tld9vsfrnyytettsb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--l-vYeASD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g16tld9vsfrnyytettsb.png" alt="HomerSimpson confused" width="520" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Digging (deeper) into the data
&lt;/h2&gt;

&lt;p&gt;To get to the bottom of this we had to dive into the &lt;a href="https://support.lob.com/hc/en-us/articles/5131590984339-Address-Verification-Why-is-my-address-undeliverable-"&gt;USPS deliverability&lt;/a&gt; analysis. USPS includes details in their metadata including Delivery Point Validation (DPV) information for each address. This includes “dpv_footnotes,” or 2-character codes that provide more specific information or context. (For example, there is a footnote code to indicate the address input matches a military address or a code to indicate a valid address that is missing a secondary number.) DPV is intended to confirm USPS addresses but also identify potential addressing issues that may hinder delivery. These codes are one of many variables factored into USPS decision-making when determining deliverability.&lt;/p&gt;

&lt;p&gt;Though not shown in our sample address, one of the DPV footnotes present was ”R7”, which is short for Carrier Route R777, also known as Phantom Route. If you get asked on trivia night, this is code for an “address confirmed but assigned to phantom route R777 or R779 and USPS delivery is not provided” (&lt;a href="https://postalpro.usps.com/mnt/glusterfs/2021-06/CASS_CycleO_ExecutiveSummary_0.pdf"&gt;source&lt;/a&gt;). In practice, this means the mailpiece is not eligible for “street delivery” but is still technically deliverable at the doorstep. &lt;strong&gt;Addresses with the R7 footnote are still deemed deliverable by the USPS since they are able to receive mail at the door. That is, these addresses are &lt;em&gt;technically&lt;/em&gt; deliverable but &lt;em&gt;practically&lt;/em&gt; undeliverable.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BNahTiyZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdz1sngk3tjpa9hxfg6b.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BNahTiyZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdz1sngk3tjpa9hxfg6b.gif" alt="theDude" width="480" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Historically, our AV team followed suit. That is, when this data was present, we also reported this address as deliverable. &lt;/p&gt;

&lt;p&gt;But when our customer became buried in returned postcards, &lt;strong&gt;we determined Lob needed to change our response when we see the R7 DPV footnote. If &lt;em&gt;any&lt;/em&gt; part of the USPS response says “undeliverable,” then we should err on the side of caution and note it as undeliverable&lt;/strong&gt;. The customer still has all the data available in the response and can choose to proceed or not.&lt;/p&gt;

&lt;p&gt;Problem solved, right? &lt;/p&gt;

&lt;p&gt;Not entirely. We still had returned to sender mail &lt;em&gt;where R7 was not present!?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vfec0sX5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6lqocjrnf7a6i8mszprm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vfec0sX5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6lqocjrnf7a6i8mszprm.png" alt="visible confusion" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Digging (deepest) into the data
&lt;/h2&gt;

&lt;p&gt;We opened the investigation to our contacts at the USPS to determine why these addresses were indicated as deliverable by the USPS, had been delivered to previously (that is, had a 100% Lob confidence score), and lacked the R7 code—but were &lt;em&gt;still&lt;/em&gt; being returned. &lt;/p&gt;

&lt;p&gt;The USPS informed us they were returning these to the sender based on a different identifier on the mailpiece:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“The Service Type Identifier (STID) came back as a Full-Service First Class Mail Piece using Informed Visibility with No Address Corrections. In this case, we return the pieces if they are undeliverable based on the address on the mail piece. That could be due to the customer moving, the address could be missing a suite or apartment number, etc.“&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sure, these reasons &lt;em&gt;could&lt;/em&gt; be valid, but since all data prior to the send predicted a successful delivery, &lt;em&gt;were they&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;Our team did not let up. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hQLFfKwA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/px613bys0xwgwbo78gyi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hQLFfKwA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/px613bys0xwgwbo78gyi.png" alt="never surrender" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We sent 10 specific addresses to the USPS for a deeper dive and we eventually got details from individual mail carriers. For our samples in Burlington, WI, “neither address has a mailbox.” For zips in 98947, they reported “no street delivery for any address in the city of Tieton, WA.” This makes sense; in any geolocation, there may be a whole suite of houses that don't have mailboxes. Ideally, the USPS could update each of these addresses to reflect their R7 status, but understandably they do not have the resources to tackle such a problem. And as we have learned, even with the correct DPV code, the USPS would still return a prediction of “deliverable” if queried.&lt;/p&gt;

&lt;p&gt;That leads us to the unfortunate truth, which is that USPS data on deliverability is prone to imperfection.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TC1EWkJY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/em2wamfiysgwusz9dgol.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TC1EWkJY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/em2wamfiysgwusz9dgol.png" alt="pobodys nerfect" width="540" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Light at the end of the tunnel
&lt;/h2&gt;

&lt;p&gt;But wait. We're not just surfacing USPS data alone, are we?&lt;/p&gt;

&lt;p&gt;Our Address Verification team is back in the ring, and look who has a chair?  Let’s not forget about the Lob Confidence Score!&lt;/p&gt;

&lt;p&gt;Now when we evaluate the same set of “problem” addresses, &lt;strong&gt;the accuracy of our Confidence Score has improved significantly.&lt;/strong&gt; For example, when we re-analyzed the addresses for the 97% campaign, the Confidence Score shifted to more closely reflect reality. The majority of these addresses now have a Confidence Score of 0. The lower the score, the lower the confidence in successful delivery. &lt;/p&gt;

&lt;p&gt;Confidence Scores have and will continue to improve as more data is collected on deliveries. &lt;strong&gt;That is, the more mail we send, the more tracking data we get, and the more valuable our Confidence Score is for our customers&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Oh, and NBD, but this customer is now seeing a 0.8% RTS rate across sends, cutting down their original amount by over half! Needless to say, both the customer and our team are thrilled with this outcome.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tOj6rvTU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p8p0ufjj0t6za4rf1qu8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tOj6rvTU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p8p0ufjj0t6za4rf1qu8.png" alt="chairshot" width="638" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;And so, this is how we gained even more confidence in the Lob Confidence score. The fact is address data is prone to faults. It's manual data subject to clerical error and imperfections and many elements are outside of our control. It would be easy to simply roll over and accept this. But here at Lob, we own the outcome; to stand behind having the most comprehensive deliverability data available in the market, our commitment to product improvement will never cease. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Content adapted from a presentation by Solution Engineer Michael Morgan.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>directmail</category>
      <category>usps</category>
      <category>todayilearned</category>
      <category>api</category>
    </item>
    <item>
      <title>Tracking changes in your Elixir code with the Audit library</title>
      <dc:creator>Lob</dc:creator>
      <pubDate>Fri, 09 Sep 2022 14:23:28 +0000</pubDate>
      <link>https://forem.com/lob/tracking-changes-in-your-elixir-code-with-the-audit-library-2lld</link>
      <guid>https://forem.com/lob/tracking-changes-in-your-elixir-code-with-the-audit-library-2lld</guid>
      <description>&lt;p&gt;Part of my work with Lob as a Staff Engineer is to implement various USPS standards pertaining to address autocorrection (aka CASS Cycle ‘N’ and ‘O’). These standards have a lot of subtle details, so the Elixir code that implements them can be intimidating. It is difficult to get this complex code to do what you want, so I started to investigate techniques that would help me track how my changes impacted the results. Before we dive into my solution, let’s first give a quick recap on how functional data structures differ from their imperative counterparts.&lt;/p&gt;

&lt;h2&gt;
  
  
  A recap on Functional Data Structures
&lt;/h2&gt;

&lt;p&gt;One of the counterintuitive aspects of writing functional code is the constructive nature of data structures. You cannot mutate an existing data structure — if you want to make a change, you must create new elements containing the change that you desire &lt;strong&gt;without altering the existing structure&lt;/strong&gt;. Essentially you make a new version of the data structure that embodies your change that will share common parts of the previous version that stayed the same.&lt;/p&gt;

&lt;p&gt;Let’s take the concrete example of a binary tree. The tree, T1, has six nodes: A, B, C, D, E, and F:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EIR2Ea-o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jkb7j88w916d2eu8lm6r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EIR2Ea-o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jkb7j88w916d2eu8lm6r.png" alt="Destructive vs constructive binary tree insertion" width="864" height="710"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Destructive vs constructive binary tree insertion&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The traditional imperative way to add G to this tree would be to overwrite the right field of the parent node containing F to point to a newly created node containing G as illustrated by the left side of the diagram. But in a functional language like Elixir, we don’t have the ability to overwrite fields. Instead, we must create a new spine for the tree containing the nodes leading to the newly created G node while sharing the unchanged nodes from the original tree, T1.&lt;/p&gt;

&lt;p&gt;At first, this seems terribly inefficient — after all the imperative counterpart just needed to perform one (destructive) update. But it’s not all bad news in the functional world; although in the tree example we create log₂(n) extra nodes, our old version and new version can happily co-exist. In fact, by merely keeping a pointer to old versions of the tree, we can trivially implement an infinite undo facility (space permitting). In the imperative world, this is a bit trickier — we would need to record the changes via a list of deltas and rerun those deltas if we wanted to access an earlier state. In the functional world, all these versions can simultaneously coexist for a small investment in space. Neat! In the imperative world? Not so much…&lt;/p&gt;

&lt;p&gt;This also illustrates why it’s easier to reason about functional code versus imperative code — in the functional code, everything stays constant. In the imperative code, a pointer to a structure is no guarantee that structure will remain intact. This is also the reason that imperative concurrent programming is fraught with difficulty whereas functional programming lends itself quite naturally to working in parallel. No need for critical sections, semaphores, monitors, etc. Those constructs are all about protecting some piece of code that side effects from being executed simultaneously in different threads/processes. No side effects — no problem!&lt;/p&gt;

&lt;p&gt;Implementing data structures functionally is actually a fascinating area of study — I highly recommend Chris Osaki’s Ph.D. thesis on the topic:&lt;a href="https://www.amzn.com/0521663504"&gt; Purely Functional Data Structures&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Now that we’ve refreshed our understanding of functional techniques for implementing data structures, we can move forward with an explanation of the basic idea. The core of our program doing address correction manipulates a very large &lt;strong&gt;%Address{}&lt;/strong&gt; struct. The program works in phases and in each phase a different set of fields are populated with new metadata either deduced from machine learning sub-systems, or extracted from various USPS- supplied metadata. The final Address struct will have the answer we seek (we hope) and a whole lot of additional metadata that might help us understand how it came to that conclusion. But with a data structure as large as Address with over a hundred fields, IO.inspect at some strategic locations isn’t going to cut it — the amount of information printed to the screen is just too overwhelming. We need something a little bit more precise.&lt;/p&gt;
&lt;h2&gt;
  
  
  Making a difference
&lt;/h2&gt;

&lt;p&gt;A key component of our library is the difference engine. This takes two objects and computes a list of the differences between them which makes it easier for us to visualize the evolution of our data structures over time. First, let’s decide how to represent a difference. A delta has three possibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;update&lt;/strong&gt;, replace one item with another&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;delete&lt;/strong&gt;, remove an item&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;add&lt;/strong&gt;, add an item&lt;/li&gt;
&lt;/ul&gt;


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


&lt;p&gt;With our representation settled, we need to figure out our implementation strategy. We simultaneously recurse over the two objects we wish to compare until we find a point where they diverge and we return the difference. So our main function will look like this:&lt;/p&gt;


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


&lt;p&gt;This delegates to a three-argument version that takes the current path as the first argument. We flatten the result, reverse the paths (because they’re call stacks) and then order the results by length of path.&lt;/p&gt;

&lt;p&gt;So now we tackle the major cases for delta: struct, map, list, tuple, and everything else:&lt;/p&gt;


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


&lt;p&gt;Let’s tackle these one at a time. For structs, if they are the same kind of struct, convert them into a map and carry on; otherwise, they’re already different.&lt;/p&gt;


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


&lt;p&gt;For maps, we want to compute the overlap of keys: a_only, only in a; b_only, only in b; and common, appears in both. With that information, it’s simply a matter of computing the difference of the values of the common keys:&lt;/p&gt;


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


&lt;p&gt;For lists, I take a simplistic approach of zipping them together to compare the elements pairwise. (I think computing the Levenshtein edits would be overkill).&lt;/p&gt;


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


&lt;p&gt;For tuples, if they are the same size convert them to lists and let delta_list process them, otherwise, they’re different.&lt;/p&gt;


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


&lt;p&gt;This just leaves the base case and we’re done.&lt;/p&gt;


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


&lt;p&gt;Ok, I suppose we should define empty?&lt;/p&gt;


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


&lt;p&gt;And that wraps up our description of our difference engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Elixir’s macro magic
&lt;/h2&gt;

&lt;p&gt;A lot of folks don’t realize that Elixir is actually quite a small language; most of the features that you know and love are implemented via the powerful macro languages. Don’t believe me? Check out all the language features implemented inside the Kernel module: @variables, def, defdelegate, defexception, defguard, defimpl, defmacro, |&amp;gt; etc., etc. This also explains why the syntax feels is a little clunky in places — it’s likely implemented as a macro!&lt;/p&gt;

&lt;h2&gt;
  
  
  The basic idea
&lt;/h2&gt;

&lt;p&gt;Our basic idea is to define a macro, audit, which embeds a paper trail inside an &lt;strong&gt;audittrail&lt;/strong&gt; field. It needs to be a macro because we want to capture the file and line number from where the macro was called. Basically, this field will contain a triple of: the last version of the data structure, the filename, and the line number.&lt;/p&gt;

&lt;p&gt;Let’s get started by defining some types:&lt;/p&gt;


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


&lt;p&gt;So our audit consists of a snapshot of the struct, a filename, and a line number. It’s considered good macro design practice to isolate as much of the functionality as possible in a function. So here’s our audit_fun:&lt;/p&gt;


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


&lt;p&gt;So now our macro is extremely simple:&lt;/p&gt;


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


&lt;p&gt;To put it to use, we want to be able to generate a changelist given a struct that has our magic &lt;strong&gt;audit_trail&lt;/strong&gt; field:&lt;/p&gt;


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


&lt;p&gt;Now we just need some helper functions to turn that changelist into a human-readable string:&lt;/p&gt;


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


&lt;p&gt;There are two nuances in this code. Firstly, as we’ll be looking up our sources files a lot, it behooves us to cache them. So we implement a simple Agent to accomplish that:&lt;/p&gt;


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


&lt;p&gt;Secondly, by their very nature source files are subject to change. So for this information to be useful at a later point in time, we should use git SHAs to specify the version of the file that we’re looking at. To accomplish this, we have a module to do all the GitHub URL wrangling:&lt;/p&gt;


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


&lt;h2&gt;
  
  
  The final result
&lt;/h2&gt;

&lt;p&gt;With all these components in place, we’re finally in a position to demonstrate our library in action.&lt;/p&gt;


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


&lt;p&gt;This is a somewhat trivial example. The ideal use case is for much larger struct where it becomes hard to see the wood from the trees when you IO.inspect results. The use of the difference engine shines in these scenarios.&lt;/p&gt;


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


&lt;p&gt;For extremely large %Address structs in our Address Verification code, this library has saved my bacon on several occasions. I hope you find it equally useful.&lt;/p&gt;

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

&lt;p&gt;We’ve described a simple library to track changes to data structures. If you’d like to kick the tires and try it out, you can find it in hex.pm under&lt;a href="https://hex.pm/packages/audit"&gt; audit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;‍&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>audit</category>
      <category>hexpm</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>DEI+Tech event in ATL: REFACTR.TECH</title>
      <dc:creator>Lob</dc:creator>
      <pubDate>Thu, 01 Sep 2022 14:39:15 +0000</pubDate>
      <link>https://forem.com/lob/deitech-event-in-atl-refactrtech-4kni</link>
      <guid>https://forem.com/lob/deitech-event-in-atl-refactrtech-4kni</guid>
      <description>&lt;p&gt;Everything you need to know about this event is summarized in the logo: A super-fun, colorful mashup of Diversity, Inclusion, and Tech. &lt;a href="https://www.refactr.tech/"&gt;REFACTR.TECH&lt;/a&gt; is all about growing and showcasing powerful voices of marginalized people and allies in tech. The event will focus on technology while creating a safe space for thoughtful and nuanced conversations around diversity, inclusion, and intersectionality in tech. This three-day event is a combination of workshops, keynote, and track sessions by an &lt;a href="https://www.refactr.tech/speakers"&gt;incredible lineup of speakers&lt;/a&gt; to expand knowledge, hone technical skills, and make important connections.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The event is held in Atlanta, GA, from September 14-16.&lt;/strong&gt; Staff Engineer &lt;a href="https://www.linkedin.com/in/erindoyle8080/"&gt;Erin Doyle&lt;/a&gt; will be presenting on behalf of Lob; we caught up with her to learn more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why do you want to speak at this event?
&lt;/h2&gt;

&lt;p&gt;I've been to the CONNECT.TECH conference a number of times in the past and it's an amazing conference. When the same organizers started up REFACTR.TECH I was immediately excited and wanted to experience a conference that brings tech together with a focus on diversity and inclusion. It's really an honor for me years later to be able to speak at a conference that literally hands the mic over to marginalized folks to give them a space to be heard. As a female in a technical leadership position, I feel like it's important for me to speak at events like this and show other women that they can achieve success in this industry.”&lt;/p&gt;

&lt;h2&gt;
  
  
  What are you going to talk about?
&lt;/h2&gt;

&lt;p&gt;Joining a new company as a Staff+ Software Engineer—versus being promoted at a company you've been at for a while—can be an invitation for an Imposter Syndrome attack. I'll cover tips for battling that Imposter Syndrome and ways that you can be the most effective in your role to hit the ground running. Key takeaways will include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to fight imposter syndrome that spikes when starting a new job &lt;/li&gt;
&lt;li&gt;How to get ramped up on the new technical landscape quickly &lt;/li&gt;
&lt;li&gt;Methods for building rapport more quickly &lt;/li&gt;
&lt;li&gt;How to be useful and effective quickly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dDssA3na--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8k2gn3wpbm2vur39n54g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dDssA3na--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8k2gn3wpbm2vur39n54g.png" alt="Erin Doyle at Refactr" width="880" height="873"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Which talk are you most excited about and why?
&lt;/h2&gt;

&lt;p&gt;Of &lt;a href="https://www.refactr.tech/speakers"&gt;all the presentations at Refactr&lt;/a&gt;, I'm most looking forward to attending Jameson Hampton’s "Reframing Shame &amp;amp; Embracing Mistakes.” For too many years I personally struggled with a self-imposed pressure to outwardly portray perfection in everything I did. I didn't realize that I, in turn, was modeling and sending that message to more junior folks that they also had to be perfect—or else they were less-than. Since learning how incredibly toxic that way of thinking and acting was, I've been working to try to model and encourage others to see that perfection is a myth, we're all human, we all make mistakes and it's totally ok. So I'm really excited to hear more on that topic and other ways I can battle my Imposter Syndrome-induced perfectionism and help others do the same.&lt;/p&gt;

&lt;p&gt;It’s not too late to &lt;a href="https://reg.connectevents.io/ConnectEvents/rtech2022/"&gt;get tickets&lt;/a&gt; to this unique event!&lt;/p&gt;

</description>
      <category>career</category>
      <category>dei</category>
      <category>womenintech</category>
      <category>conference</category>
    </item>
    <item>
      <title>Swipe Right on Redshift</title>
      <dc:creator>Lob</dc:creator>
      <pubDate>Thu, 25 Aug 2022 16:30:00 +0000</pubDate>
      <link>https://forem.com/lob/swipe-right-on-redshift-3b7g</link>
      <guid>https://forem.com/lob/swipe-right-on-redshift-3b7g</guid>
      <description>&lt;p&gt;As a “top pick” in the direct mail automation space, Lob prints and sends millions of mailpieces each year. As such, it's been Lob's commitment from the get-go to minimize our footprint and build a more sustainable approach to direct mail. As a part of this initiative, in addition to ensuring we use responsibly sourced raw materials, &lt;strong&gt;we plant two trees for every one that we use in the production of our mailpieces, through our partnership with &lt;a href="https://www.edenprojects.org/"&gt;Eden Reforestation Projects&lt;/a&gt;&lt;/strong&gt;.  (For more on our partnership check out this blog post/video: &lt;a href="https://www.lob.com/blog/sustainability-lob-eden-reforestation-projects-partnership"&gt;“The Power of Trees: Partnering for Positive Environmental and Community Impact.”&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;As awesome as that is, this is not a sustainability story, it’s about the technology we use to accomplish this and why we shifted (excuse the pun) tools.&lt;/p&gt;

&lt;p&gt;During a hackathon in 2017, our Engineering team developed automation that runs every month. This involves a Postgres query that pulls how much mail (letters, checks, postcards, etc.) Lob sent the previous month, a calculation the equivalent in paper, which is then translated into a number of trees. (Then we offset our carbon footprint by sponsoring Eden Reforestation to plant double that number of trees.)&lt;/p&gt;

&lt;p&gt;Snippet of query:&lt;/p&gt;


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


&lt;p&gt;The result is a summary communication sent via Slack to the community and stakeholders.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KbALN_Fm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5elpcgvc7gvj6ghfchc3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KbALN_Fm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5elpcgvc7gvj6ghfchc3.png" alt="slack message showing donation to Reforestation" width="880" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, as of late, &lt;strong&gt;the automation was pretty flaky&lt;/strong&gt;: often the query would time out requiring an engineer to run it again in off-peak hours when the Postgres database was under less load;  this is not a scalable solution and was yielding less and less success.&lt;/p&gt;

&lt;p&gt;Since the query looked fine (accurate), an initial idea was to break it out into separate queries and then combine the results. But this and some other brainstorms just seemed like a hack; sure they would fix the current problem but &lt;em&gt;eventually,&lt;/em&gt; those would get big enough to timeout too. So, Platform Engineer Elijah Voigt turned to the rubber ducky channel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--auJExGGy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hsmw2shve7wn7x1dy8f4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--auJExGGy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hsmw2shve7wn7x1dy8f4.png" alt="reforestation query is failing" width="586" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter Jack Meussdorfer from the Data team to the rescue.&lt;/p&gt;

&lt;p&gt;His keen sense of smell indicated this was a too much/big data problem. He suggested instead of running the query against &lt;a href="https://www.postgresql.org/"&gt;PostgreSQL&lt;/a&gt;, we run it instead against &lt;a href="https://aws.amazon.com/redshift/"&gt;Amazon Redshift&lt;/a&gt;, which the Data team already uses for similar data pipelines.&lt;/p&gt;

&lt;p&gt;In short, Postgres is a transactional db, whereas Redshift is specifically designed for large-scale data storage and analysis. Because Redshift stores data in a different way—it uses column stores instead of row stores—there is less overhead on queries spanning a large selection of data. (This also means differences in how constraints and indexes are implemented.)  Redshift also has a higher capability of processing large amounts of data because it runs &lt;a href="https://docs.aws.amazon.com/redshift/latest/dg/c_challenges_achieving_high_performance_queries.html#massively-parallel-processing"&gt;massively parallel processing&lt;/a&gt; (MPP). If you want a deep dive check out this article &lt;a href="https://www.integrate.io/blog/redshift-vs-postgres/"&gt;Redshift vs. Postgres: Detailed Comparison of Performance and Functionality&lt;/a&gt;.) &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NYq7AwBl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y80hhsy7p7kydmmpgyrr.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NYq7AwBl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y80hhsy7p7kydmmpgyrr.jpeg" alt="little mermaid brushing hair with fork" width="227" height="222"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Is that really the right tool for the task?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Since the necessary data already exists in Redshift, Jack suggested we: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Update the query to be compatible with Redshift (alter the query syntax and add the proper schema/tables) and then&lt;/li&gt;
&lt;li&gt; Point the Reforestation query at Redshift.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Boom!** Lifting and shifting the data source from Postgres to Redshift solved the problem immediately: the query went from timing out after 1 hour to literally taking 40 seconds.**&lt;/p&gt;

&lt;p&gt;The takeaway? Rubber ducky rules. And, it’s important to evaluate whether you are using the right tool for the job at hand. Postgres was the right tool for the job in 2017, but our data changed—which means our problem changed. While we are &lt;a href="//www.lob.com/blog/kickstart-your-engineering-book-club"&gt;big fans of Postgres&lt;/a&gt;, our automation needed to scale to our new needs, so for this important query, it’s Redshift FTW. &lt;/p&gt;

</description>
      <category>redshift</category>
      <category>postgres</category>
      <category>todayilearned</category>
      <category>database</category>
    </item>
    <item>
      <title>Use Klaviyo to Automate Direct Mail</title>
      <dc:creator>Lob</dc:creator>
      <pubDate>Tue, 23 Aug 2022 16:57:30 +0000</pubDate>
      <link>https://forem.com/lob/use-klaviyo-to-automate-direct-mail-3c2m</link>
      <guid>https://forem.com/lob/use-klaviyo-to-automate-direct-mail-3c2m</guid>
      <description>&lt;p&gt;Klaviyo is a popular marketing automation software tool that empowers its customers to build campaign automation by &lt;br&gt;
syncing their tech stack with their ecommerce store to scale their communications and business. While it has traditionally served as a great resource for automated email and SMS, direct mail has historically been missing from the equation. &lt;/p&gt;

&lt;p&gt;But marketers now have a solution: integrating Lob’s direct mail APIs. &lt;strong&gt;Klaviyo can connect to Lob to automate direct mail at scale in the same manner that they are automating their digital channels today&lt;/strong&gt;: Klaviyo’s webhook functionality within their &lt;em&gt;Flows&lt;/em&gt; interface provides an easy connection point to trigger mailpieces. &lt;/p&gt;

&lt;p&gt;Read on for a walkthrough of exactly how to connect Klaviyo with Lob.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;We’ll assume you have a &lt;a href="https://www.klaviyo.com/"&gt;Klaviyo&lt;/a&gt; account and you have already registered with &lt;a href="https://dashboard.lob.com/"&gt;Lob&lt;/a&gt; (to get your API Keys) to follow along. &lt;/p&gt;

&lt;p&gt;In order to send mail from Klaviyo, Lob requires a mailing address to be stored for each profile. Klaviyo does not require mailing addresses by default, so before attempting to send mail, ensure that your recipient profiles contain properties for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Address&lt;/li&gt;
&lt;li&gt;City&lt;/li&gt;
&lt;li&gt;State&lt;/li&gt;
&lt;li&gt;Zip Code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vYwp8jLP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/080nzpko2e5xxw2b1cim.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vYwp8jLP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/080nzpko2e5xxw2b1cim.png" alt="klavioA" width="880" height="743"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are importing new profiles to Klaviyo, simply ensure that you have CSV columns for each of those properties. An example CSV to import Profiles is &lt;a href="https://s3.us-west-2.amazonaws.com/public.lob.com/solutions/Klaviyo_demo/Klaviyo_demo_Contacts_10.csv"&gt;linked here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating a Flow
&lt;/h2&gt;

&lt;p&gt;In the Klaviyo dashboard’s left-hand panel, click on &lt;strong&gt;Flows&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZJHwtHR8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5hwngx8h6148l4qm2bf5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZJHwtHR8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5hwngx8h6148l4qm2bf5.png" alt="KlavioB" width="880" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click to &lt;strong&gt;Create Flow,&lt;/strong&gt; then select a predefined goal, or click &lt;strong&gt;Create From Scratch&lt;/strong&gt; and give your flow a name.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jsu9IHNf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rw3ln220qke2fstbbr9n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jsu9IHNf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rw3ln220qke2fstbbr9n.png" alt="KlavioC" width="880" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Sq39VbGp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pbdtgzji97yi3ffaetjf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Sq39VbGp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pbdtgzji97yi3ffaetjf.png" alt="KlavioD" width="697" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Qtqq-xQO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2gub9gu783igakkrdr1y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Qtqq-xQO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2gub9gu783igakkrdr1y.png" alt="KlavioE" width="605" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choose a &lt;strong&gt;Trigger Setup&lt;/strong&gt; option in the left panel. For this example, we will be selecting &lt;strong&gt;List&lt;/strong&gt;, and choosing a list from the resulting drop-down to trigger the flow. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--B7kjrYmD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xqxcbfe2winaa23v01l4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--B7kjrYmD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xqxcbfe2winaa23v01l4.png" alt="Klavio F" width="759" height="447"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Create Webhook
&lt;/h2&gt;

&lt;p&gt;Once you have configured your trigger, an Actions menu will appear on the left panel. Select &lt;strong&gt;Webhook&lt;/strong&gt;, and drag it into your flow beneath the trigger.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--t7Rswpjd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/95sr034feynbvu4ulc3b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--t7Rswpjd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/95sr034feynbvu4ulc3b.png" alt="KlavioG" width="760" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select the Webhook in your Flow, and the configuration panel will appear on the left side. You can configure it as follows:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Destination URL:&lt;/strong&gt; This is where you will enter the Lob API URL for the form factor you would like to trigger. In this example we’ll choose postcards: &lt;a href="https://api.lob.com/v1/postcards"&gt;https://api.lob.com/v1/postcards&lt;/a&gt;. (Note that Lob has separate endpoints for postcards, letters, checks, and self-mailers.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Headers:&lt;/strong&gt; This is where you will enter the headers Lob requires in order to correctly process and authenticate the incoming requests. You will be required to enter at least two headers: Content-Type and Authorization. Click &lt;strong&gt;Add Header.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;Content-Type Header&lt;/strong&gt;, enter the following:&lt;/p&gt;

&lt;p&gt;Key: Content-Type&lt;br&gt;
Value: application/json&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;Authorization&lt;/strong&gt;, you will need your API Keys. &lt;/p&gt;
&lt;h2&gt;
  
  
  Adding API Key
&lt;/h2&gt;

&lt;p&gt;For the Authorization header, you will need to have your Base64-encoded Lob API key ready to enter, so Lob can associate the request with your account. Retrieve these credentials from your Lob dashboard by clicking on the Settings menu on the sidebar, then clicking on the API Keys tab.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IMPORTANT: ENSURE YOU ARE USING YOUR TEST API KEY.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;You can use a &lt;a href="https://www.base64encode.org/"&gt;Base64 Encoder tool&lt;/a&gt; to encode your (test) API Key. &lt;/p&gt;

&lt;p&gt;For example, if your API key was test_0dc8d51e0acffcb1880e0f19c79b2f5b0cc, you would enter test_0dc8d51e0acffcb1880e0f19c79b2f5b0cc: into a Base64 Encoder tool, the result of which would be something like dGVzdF8wZGM4ZDUxZTBhY2ZmY2IxODgwZTBmMTljNzliMmY1YjBjYzo=&lt;/p&gt;

&lt;p&gt;Note that we added a “:” to the end of the API key before encoding it, please ensure you do the same.&lt;/p&gt;

&lt;p&gt;Once you’ve encoded your API key, add the Authorization Header and use the encoded key as the value. For example:&lt;/p&gt;

&lt;p&gt;Key: Authorization&lt;br&gt;
Value: Basic dGVzdF8wZGM4ZDUxZTBhY2ZmY2IxODgwZTBmMTljNzliMmY1YjBjYzo=&lt;/p&gt;
&lt;h2&gt;
  
  
  JSON body
&lt;/h2&gt;

&lt;p&gt;This is where you will pass Lob the information that informs the mailpiece. The field names (name, address_line1, etc.) represent the information that Lob’s API will require. The values on the right represent what you will pass to Lob to populate the given field. &lt;/p&gt;

&lt;p&gt;The values can be mapped to attributes from Klaviyo profiles by using Klaviyo’s Profile Variables. You can find the Profile Variables for each attribute by taking the following steps:&lt;/p&gt;

&lt;p&gt;First, click on the linked word &lt;strong&gt;profile&lt;/strong&gt; in ‘View profile properties’, directly beneath the JSON Body input field.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fegRh_9Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9f2s42qddq75hkm6v0wi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fegRh_9Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9f2s42qddq75hkm6v0wi.png" alt="KlavioH" width="334" height="192"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will pull up an example Profile. Clicking on an attribute will automatically copy the corresponding Profile variable, which you can use to populate the appropriate value in the JSON Body. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KvHwZyAD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yyxzxo7hxism78990j6i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KvHwZyAD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yyxzxo7hxism78990j6i.png" alt="KlavioI" width="756" height="783"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An example of a populated JSON Body could look something like the following. (You can find more information on each of these fields in &lt;a href="https://docs.lob.com"&gt;Lob’s API documentation&lt;/a&gt;.)&lt;/p&gt;


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


&lt;h2&gt;
  
  
  Update Creative
&lt;/h2&gt;

&lt;p&gt;Note that if you are copying the above example, you will want to replace the values being passed for ‘front’ and ‘back’ with your own Lob template IDs or linked creative.  For example: &lt;/p&gt;

&lt;p&gt;"front": “&lt;a href="https://s3.us-west-2.amazonaws.com/public.lob.com/lobcom/template_gallery/gtmpl_524eb6ff6c6998_Increase_Customer_Loyalty_Postcard_4x6_front.html"&gt;https://s3.us-west-2.amazonaws.com/public.lob.com/lobcom/template_gallery/gtmpl_524eb6ff6c6998_Increase_Customer_Loyalty_Postcard_4x6_front.html&lt;/a&gt;"&lt;/p&gt;

&lt;p&gt;"back": "&lt;a href="https://s3.us-west-2.amazonaws.com/public.lob.com/lobcom/template_gallery/gtmpl_524eb6ff6c6998_Increase_Customer_Loyalty_Postcard_4x6_back.html"&gt;https://s3.us-west-2.amazonaws.com/public.lob.com/lobcom/template_gallery/gtmpl_524eb6ff6c6998_Increase_Customer_Loyalty_Postcard_4x6_back.html&lt;/a&gt;"&lt;/p&gt;

&lt;p&gt;(For more about designing/formatting creative for Lob, &lt;a href="https://help.lob.com/mail-piece-design-specs/postcards-self-mailers"&gt;go here&lt;/a&gt;.) &lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;You can test that your webhook is configured correctly by clicking on &lt;strong&gt;Preview Webhook&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--paLfojAa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/77w6ozw34i3dgfy4w75w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--paLfojAa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/77w6ozw34i3dgfy4w75w.png" alt="KlavioJ" width="344" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The resulting panel will show an example of the data being passed to Lob for a specific Profile. You can look at the JSON to verify that the Klaviyo Profile Variables are being correctly replaced. For example, instead of person|lookup:'$address1'|default:'' you will see an address, such as &lt;strong&gt;185 Berry St.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--319Dfy3s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b4mql23f7pjssmiwq76a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--319Dfy3s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b4mql23f7pjssmiwq76a.png" alt="KlavioK" width="421" height="870"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If this all looks correct, double-check that you used your Test API Key (rather than your live one), then click on &lt;strong&gt;Send Test Request&lt;/strong&gt;. If it was configured correctly, you will see the following message:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yJ66CDYh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/72ty5bihfynpb7oq892a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yJ66CDYh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/72ty5bihfynpb7oq892a.png" alt="KlavioL" width="480" height="59"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can then log into your Lob account to verify that your postcard was generated. (The below is the result of the example creative.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZUUViDm5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l6xbq56yv7a7vkqt51yw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZUUViDm5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l6xbq56yv7a7vkqt51yw.png" alt="postcard front" width="880" height="607"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--x_bDQ5Cf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pszo27rwcluwo7ep1zds.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--x_bDQ5Cf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pszo27rwcluwo7ep1zds.png" alt="postcard back" width="880" height="622"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Go time!
&lt;/h2&gt;

&lt;p&gt;You will need to add payment information to your Lob account should you actually wish to print and send mailpieces. When you are ready, you can replace your Test API key with your Live API key; &lt;strong&gt;if you have your LIVE API key in the template it will create mail.&lt;/strong&gt; If you are using other test resources like address IDs or template IDs, those will also need to be transferred to your live environment.&lt;/p&gt;

&lt;p&gt;Make sure when you are testing that your &lt;a href="https://lob.helpjuice.com/use-case-guides/override-cancellation-window"&gt;cancellation windows&lt;/a&gt; are set to an ample time (2 hours is recommended) so you can cancel mail if you make a mistake.&lt;/p&gt;

&lt;p&gt;Have fun exploring this integration; if you have questions or need support, don’t hesitate to &lt;a href="https://lob.com/contact"&gt;contact us&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>klaviyo</category>
      <category>ecommerce</category>
      <category>api</category>
    </item>
    <item>
      <title>How Docker memory caching works &amp; finding a hidden hapi vulnerability</title>
      <dc:creator>Lob</dc:creator>
      <pubDate>Wed, 10 Aug 2022 21:20:00 +0000</pubDate>
      <link>https://forem.com/lob/how-docker-memory-caching-works-finding-a-hapi-vulnerability-nd5</link>
      <guid>https://forem.com/lob/how-docker-memory-caching-works-finding-a-hapi-vulnerability-nd5</guid>
      <description>&lt;p&gt;It was a dark and stormy night in May when our internal metric readings for memory usage spiked in our Address Verification service for no obvious reason. Even weirder than that, our largest API and main service had suddenly started crashing and restarting every several hours—a dark night indeed. The following is a summary of our painful investigation over several weeks, involving multiple team members and endless :confused_dog: and :computer_rage_bang_head: emojis. But (spoiler alert!), after the storm comes a rainbow. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR: We learned how Docker memory caching works and found  a 7-year-old file management bug which led to a secret hidden hapi vulnerability; all of which helped stabilize our containers and product offering.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need to know about Docker
&lt;/h2&gt;

&lt;p&gt;Traditionally for those who have worked with &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; and &lt;a href="https://www.datadoghq.com/" rel="noopener noreferrer"&gt;Datadog&lt;/a&gt;, the main way to measure memory usage in a container is to look for Docker.mem.in_use. Now for reasons unbeknownst to me, Docker.mem.in_use used to only measure Rss memory usage rather than the combination of both Docker.mem.rss and Docker.mem.cache: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Docker.mem.cache:&lt;/strong&gt; The amount of memory that is being used to cache data from disk (e.g., memory contents that can be associated precisely with a block on a block device).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker.mem.rss:&lt;/strong&gt; The amount of non-cache memory that belongs to the container's processes.; used for stacks, heaps, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RSS memory usage is essentially what most would think about when they see a memory leak; a piece of the heap not being garbage collected, or an object that continues to grow infinitely. And that’s why a large chunk of our investigation looked towards traditional sources of memory leaks. But after several weeks with little progress, I started to piece together why our container memory readings had spiked and why the memory leak in our main service had started at roughly the same time.&lt;/p&gt;

&lt;p&gt;During my investigation I discovered that &lt;strong&gt;on May 9th, Datadog made a change in how memory data is read: mem_in_use is now a combination of &lt;em&gt;both&lt;/em&gt; rss and cache usage.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;This was when a lightbulb went off: &lt;strong&gt;None of our Docker containers had any mounts of volumes attached, so whenever we wrote to disk, we would be writing to memory instead.&lt;/strong&gt; Logically it makes quite a bit of sense, if you don’t have disk space the only other place you can write to is memory(ram). &lt;/p&gt;

&lt;p&gt;This actually even goes a step further, there might be instances where you may have a mount or volume but you still notice docker.mem.cache going up. &lt;strong&gt;Well certain linux distributions will freely cache disk read/writes to memory if there is ample space, and will automatically start freeing it up once you start running low.&lt;/strong&gt; For a deeper breakdown, here’s a great article: &lt;a href="https://www.linuxatemyram.com/" rel="noopener noreferrer"&gt;Linux Ate my RAM!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { promises as fs } from 'fs' 

await fs.writeFile(...)

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

&lt;/div&gt;



&lt;p&gt;…wasn’t writing to disk but straight to memory. Specifically for our Address Verification Service, we load nearly 4 gigabytes of files into memory at runtime in order to operate. &lt;/p&gt;

&lt;p&gt;Now this helped us identify and resolve the problem in our Address Verification service: we were improperly using cached Docker builds. Once we pushed a fix for that, our memory readings went back to normal. For those that are confused why that might be the case, the details need to be kept in house, but in short, &lt;strong&gt;if our assets had been properly cached as a docker layer, the 4 GB of data in that case would be counted as part of the image size and would be part of &lt;a href="https://docs.Docker.com/storage/storagedriver/" rel="noopener noreferrer"&gt;Docker’s storage driver&lt;/a&gt;. That way it would no longer be using memory to store those files and instead would properly be a part of the disk space.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need to know about hapi file management
&lt;/h2&gt;

&lt;p&gt;With one service fixed, I just had to fix the memory problem in our core API. To understand how I fixed it, I think it’s relevant to provide some background into &lt;a href="https://hapi.dev/" rel="noopener noreferrer"&gt;hapi.js&lt;/a&gt;. We use hapi &lt;a href="https://www.lob.com/blog/javascript-framework-updates" rel="noopener noreferrer"&gt;as our main framework&lt;/a&gt; for our api and are currently running v20. As a company that deals heavily with turning assets into printable mail pieces we needed a framework that makes it easy to work with &lt;a href="https://hapi.dev/api/?v=20.2.2#-routeoptionspayload" rel="noopener noreferrer"&gt;parsing payloads&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The main options of dealing with payloads that Hapi provides are: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Data&lt;/li&gt;
&lt;li&gt;Streams&lt;/li&gt;
&lt;li&gt;Files&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first option, Data,  is fairly straightforward: if you pass a JSON body into a Post request it’ll parse it into JSON data. Streams will do the same thing, but if you get a multipart upload (i.e., a file is uploaded), it’ll read that file into a stream without any additional tooling needed. The third is similar, but it’ll write a file to a specified directory rather than give you a stream: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;‘File’: the incoming payload is written to a temporary file in the directory specified by the &lt;em&gt;uploads&lt;/em&gt; settings. If the payload is 'multipart/form-data' and &lt;em&gt;parse&lt;/em&gt; is &lt;em&gt;true&lt;/em&gt;, field values are presented as text while files are saved to disk. Note that it is the sole responsibility of the application to clean up the files generated by the framework. This can be done by keeping track of which files are used (e.g. using the &lt;em&gt;request.app&lt;/em&gt; object), and listening to the server &lt;em&gt;'response'&lt;/em&gt; event to perform cleanup.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Now there’s two things that developers should be aware of:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;If you’re running a Docker container with no storage method, hapi will write your file to memory.&lt;/strong&gt; This goes back to what I mentioned earlier. It’s quite intuitive but you cannot go with an assumption that disk storage actually exists with how popular services like s3 are now and how volumes are built in by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The second is, that it is the sole responsibility of the application to clean up these files.&lt;/strong&gt; Unfortunately we missed this line in the docs and hadn’t actually been cleaning up those files. Now this in of itself isn’t the biggest deal breaker. There were several endpoints that were failing to delete these files after we had performed a major refactor, so I quickly cleaned those up. Immediately after those changes, our containers were showing a significant decrease in memory leaking but it was still leaking. Our containers were now crashing every 3 days rather than every 3 hours. This led me to have a final discovery…&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Hapi.js request lifecycle doesn’t make a lot of sense
&lt;/h2&gt;

&lt;p&gt;While the hapi docs do let us know that we should be deleting assets, they fail to raise a pretty important concern in that it’s &lt;strong&gt;super&lt;/strong&gt; important to listen to a server response event rather than manually handling file deletion within your normal code paths. &lt;strong&gt;The huge issue is that payload parsing occurs first in the hapi lifecycle before almost anything else—stuff like authentication and request validation happen &lt;em&gt;after&lt;/em&gt; a payload is parsed—creating somewhat of a vulnerability if you’re not careful.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Say a malicious actor, let’s call him Bob, had a personal vendetta against Lob. They  could continually try to hit our endpoints with ill-formatted requests, along with a massive 200 mb asset attached. That request payload would be parsed and the file would be written to memory. Now we’re smart enough to create some validators that will check any files attached to make sure they’re less than a certain size, let’s say 20 mb in this case. That same request would certainly fail payload validation and then we would send a 422 response. But that 200 mb of data is now sitting in memory, and with a couple more requests it would start causing our containers to run out of memory. So rather than making sure these files were deleted our normal code path and controllers, we had to make sure they were being deleted regardless of whether they made it that far in the pipeline. &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%2F7ajqu3qf265i88uq1bge.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%2F7ajqu3qf265i88uq1bge.png" alt="hapi lifecycle"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;All it took was a little chunk of code to clean up response payloads right before a response.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Say we’re handling a request like this:&lt;br&gt;
&lt;/p&gt;

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

{

“Upload_name”: “Testing”,

“file_we_want_to_upload”: “/tmp/file.html”

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;We need a response handler that’ll delete that file right before sending a response regardless of whether it was successful or not&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
module.exports = {

 name: 'cleanUpAssets',

 register: async function (server) {

   server.events.on('response', async function (request) {

     rimraf(request.payload.file_we_want_to_upload) }

};

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  The end of the rainbow
&lt;/h2&gt;

&lt;p&gt;And with these changes our memory was no longer spiking! The best part? &lt;strong&gt;Even after we shrunk our container memory limit by 1/4th, we were still seeing stable memory readings&lt;/strong&gt; Celebration ensued! Now the emojis were of the :partyporg: and :dancingpickle: variety.&lt;/p&gt;

&lt;p&gt;This reduction in cpu and memory is likely to result in AWS savings from right sizing containers; there may even be future savings when we complete our &lt;a href="https://www.lob.com/blog/alternative-to-kubernetes" rel="noopener noreferrer"&gt;migration to Nomad&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Engineers may occasionally get called stubborn, but in the Case of the Mysterious Memory Leak,  tenacity paid off. In leveling up our Docker and hapi knowledge, we uncovered a legacy bug and a potential vulnerability, and the changes we made resulted in a more stable, secure, and cost-effective operation.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>datadog</category>
      <category>hapijs</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
