Lob's website experience is not optimized for Internet Explorer.
Please choose another browser.

Arrow Up to go to top of page
Hero Image for Lob Deep Dives Blog PostMailing a Postcard With JavaScript Part 2: Add Address Autocomplete & Address Verification
Engineering
November 30, 2021

Mailing a Postcard With JavaScript Part 2: Add Address Autocomplete & Address Verification

Share this post

This is the second article in our three-part series about using Lob APIs to build an app to create and send postcards. In part one, we set up our application in Vue and Node. We also enabled our users to generate and save ready-to-mail postcards as Lob HTML Templates. Finally, we synced those templates with the Lob API.

We’ll now improve our application by enabling our users to send physical postcards to their customers. We’ll accept addresses, verify them (on both the client and server-side), then queue our postcard for delivery.

Review the first article of this series to follow along with this tutorial. Let’s dive in!

Improving our app

We’ll build on the application we started last time. If you’re coding along, make sure you have that application ready to go.

Let’s first create an AddressForm component to use in our application. We need to get the address of both our sender and our recipient to send to our server, so we’ll use this component at least twice. We’ll accept two props: a reactive address object that we can share with the parent, and a form ID. Create a new file called AddressForm.vue in the frontend/src/components folder.

<script setup>
import { toRefs, ref, onMounted } from "vue";

const props = defineProps({
address: Object,
formId: String
})

const { name, address_line1, address_line2, address_city, address_state, address_zip } = toRefs(props.address)

onMounted(()=> { console.log("onmounted hook") })
</script>

We’ll destructure individual elements from our incoming prop. We need to use the toRefs function to help us do this. If we don’t, the destructured values won’t be reactive, meaning we can’t share them with the parent.

Let’s now use these values to set up our form:

<template>
<form :id="formId">
<div class="form-field">
<label for="name">Name</label>
<input id="name" v-model="name" />
</div>
<div class="form-field">
<label for="firstLine">Address Line 1</label>
<input id="firstLine" v-model="address_line1" />
</div>
<div class="form-field">
<label for="secondLine">Address Line 2</label>
<input id="secondLine" v-model="address_line2" />
</div>
<div class="form-field">
<label for="city">City</label>
<input id="city" v-model="address_city" />
</div>
<div class="form-field">
<label for="state">State</label>
<input id="state" v-model="address_state" />
</div>
<div class="form-field">
<label for="zip">Zip Code</label>
<input id="zip" v-model="address_zip" />
</div>
</form>
</template>

Next, let’s create a parent page to use this component and select templates for our postcard’s front and back. Create a file named CreatePostcard.vue in the same folder as our previous component.

In our script section, we get our reactive variables ready. We have an object and starting values for each of our addresses, an array of templates, the ID of the front and back templates/thumbnails, and any possible error messages.

<script setup>
import { ref, onMounted } from "vue";
import AddressForm from "./AddressForm.vue";

const toAddress = ref({ name: "", address_line1: "", address_line1: "", address_city: "", address_state: "", address_zip: "" })
const fromAddress = ref({ name: "", address_line1: "", address_line1: "", address_city: "", address_state: "", address_zip: "" })
const templates = ref([]);
const frontTemplate = ref("")
const backTemplate = ref("")
const error = ref("")
const success = ref(false)
const loading = ref(false)
const frontThumbnail = ref("")
const backThumbnail = ref("")


onMounted(() => {
fetch("http://localhost:3030/templates")
.then((data) => data.json())
.then((data) => templates.value = data);
})
</script>

We use the onMounted lifecycle function to fetch the templates when our page first loads so our users can select from templates they have stored in Lob.

<template>
  <div>
  <div v-if="loading == true">LOADING...</div>
  <div v-if="error" class="error">{{ error }}</div>
  <div v-if="success == true">
      <div class="success">Postcard Created!</div>
      <div class="flex">
          <div style=""><img :src=frontThumbnail /><br /><span>Front</span></div>
          <div><img :src=backThumbnail /><br /><span>Back</span></div>
      </div>
  </div>
  <h2>Select a template:</h2>
  <div class="flex">
      <select v-model="frontTemplate" class="select">
          <option value>Please select front template</option>
          <option v-for="template in templates" :value="template.id">{{ template.description }}</option>
      </select>
      <select v-model="backTemplate" class="select">
          <option value>Please select back template</option>
          <option v-for="template in templates" :value="template.id">{{ template.description }}</option>
      </select>
  </div>
  <div class="container">
      <div class="address">
          <h2>Address you're sending to</h2>
          <AddressForm :address="toAddress" formId="toAddress" />
      </div>
      <div class="address">
          <h2>Address you're sending from</h2>
          <AddressForm :address="fromAddress" formId="fromAddress" />
      </div>
  </div>
</div>
</template>

<style lang="scss">
.container {
  display: flex;
  justify-content: space-between;
}

.flex {
  display: flex;
  justify-content: center;
}

.strikeout {
  text-decoration: line-through;
}

.address {
  width: 100%;
  margin: 14px;
}

.submit-area {
  border: 1px solid black;
  padding: 4px;
  padding-bottom: 16px;
  width: 50%;
  margin: auto;
}

.error {
  border: 1px solid;
  margin: 10px 0px;
  padding: 15px 10px 15px 50px;
  background-repeat: no-repeat;
  background-position: 10px center;
  color: #d8000c;
  background-color: #ffbaba;
  background-image: url("https://i.imgur.com/GnyDvKN.png");
}

.success {
  border: 1px solid;
  margin: 10px 0px;
  padding: 15px 10px 15px 50px;
  color: #ffffff;
  background-color: #8af89c;
}

:root {
  --select-border: #777;
  --select-focus: blue;
  --select-arrow: var(--select-border);
}

select {
  appearance: none;
  background-color: transparent;
  border: none;
  padding: 0 1em 0 0;
  margin: 0;
  width: 100%;
  font-family: inherit;
  font-size: inherit;
  cursor: inherit;
  line-height: inherit;
 
  z-index: 1;
 
  &::-ms-expand {
      display: none;
  }

  outline: none;
}

.select {
  display: grid;
  grid-template-areas: "select";
  align-items: center;
  position: relative;
  margin: 4px;

  select,
  &::after {
      grid-area: select;
  }

  min-width: 15ch;
  max-width: 30ch;

  border: 1px solid var(--select-border);
  border-radius: 0.25em;
  padding: 0.25em 0.5em;

  font-size: 1.25rem;
  cursor: pointer;
  line-height: 1.1;

  background-color: #fff;
  background-image: linear-gradient(to top, #f9f9f9, #fff 33%);
 
  &:not(.select--multiple)::after {
      content: "";
      justify-self: end;
      width: 0.8em;
      height: 0.5em;
      background-color: var(--select-arrow);
      clip-path: polygon(100% 0%, 0 0%, 50% 100%);
  }
}

select:focus + .focus {
  position: absolute;
  top: -1px;
  left: -1px;
  right: -1px;
  bottom: -1px;
  border: 2px solid var(--select-focus);
  border-radius: inherit;
}

select[multiple] {
  padding-right: 0;
  height: 6rem;
 
  option {
      white-space: normal;
      outline-color: var(--select-focus);
  }
}

.select--disabled {
  cursor: not-allowed;
  background-color: #eee;
  background-image: linear-gradient(to top, #ddd, #eee 33%);
}

label {
  font-size: 1.125rem;
  font-weight: 500;
}

.select + label {
  margin-top: 2rem;
}

.form-field {
  display: flex;
  flex-direction: column;
}

.form-field label {
  flex: 1;
  text-align: left;
}

.form-field input {
  flex: 1;
  width:100%;
}
</style>

In our template, we provide selects to allow our user to pick their templates. We also render the AddressForm twice, once for the sender and once for the recipient. Notice that we use the “lang” attribute on the “style” element. Since we are referencing Sass, we need to install the vue-loader that will handle the preprocessing for us. In the terminal, at the root of the frontend folder, run the following command:

npm install -D sass-loader sass

The final step is to give our new page a route, so let’s head over to the frontend/src/router/index.js file and modify this file so that looks like this:

import { createWebHistory, createRouter } from "vue-router";
import ListTemplates from "../components/ListTemplates.vue";
import Front from "../components/Front.vue";
import CreatePostcard from "../components/CreatePostcard.vue";

const routes = [
 { path: "/", component: Front, name: "Home" },
 { path: "/list", component: ListTemplates, name: "ListTemplates" },
 { path: "/create", component: CreatePostcard, name: "CreatePostcard" }
];

const router = createRouter({
 history: createWebHistory(),
 routes,
});

export default router;

Mailing a Postcard With JavaScript Part 2: Add Address Autocomplete & Address Verification image 2

We next use Lob’s client-side library, Address Elements, to verify and autocomplete US addresses in the browser. The app needs to load this library after the forms render. Then it searches for the correct fields and allows autocompletion as necessary.

Back in our parent component, we add this script’s mounting to our onMounted function.

onMounted(() => {
 fetch("http://localhost:3030/templates")
   .then((data) => data.json())
   .then((data) => templates.value = data);

 const script = document.createElement("script");
 script.src = "https://cdn.lob.com/lob-address-elements/2.1.3/lob-address-elements.min.js";
 script.async = true;
 script.setAttribute("data-lob-key", import.meta.env.VITE_LOB_API_KEY);
 script.setAttribute("data-lob-primary-id", "firstLine");
 script.setAttribute("data-lob-secondary-id", "secondLine");
 script.setAttribute("data-lob-city-id", "city");
 script.setAttribute("data-lob-state-id", "state");
 script.setAttribute("data-lob-zip-id", "zip");
 document.body.appendChild(script);
});

This function works great, updating the form as we’d expect. But, it doesn’t update the reactive variables. To handle that action, we need to subscribe to an event that the library makes available, then revise based on that event.

We will need to update the .env file that is the root of the frontend folder with the API keys that Lob provides us. For the address verification to work, we will need to use the “live” public keys as the “test” keys do not offer address completion. Add an entry that has the following format:

VITE_LOB_API_KEY=<insert_live_publishable_api_key>

In our AddressForm component, we add a new ref for our subscription and an event listener to our window. We do this because we can’t guarantee that the LobAddressElements library will be ready when the app mounts this component. We’ll listen for the keydown event and return early if we have the subscription or LobAddressElements isn’t available. In the frontend/src/components/AddressForm.vue let’s add the following pieces of code:

import { toRefs, ref, onMounted } from "vue";

const props = defineProps({
 address: Object,
 formId: String
});

const { name, address_line1, address_line2, address_city, address_state, address_zip } = toRefs(props.address);

const subscription = ref();
onMounted(() => {
 window.addEventListener("keydown", () => {
   if (subscription.value || !window.LobAddressElements) return;
   subscription.value = window.LobAddressElements.on('elements.us_autocompletion.selection', function (payload) {
     if (payload.form.id !== props.formId) return;

     const { selection: { primary_line, city, state, zip_code } } = payload;

     address_line1.value = primary_line;
     address_city.value = city;
     address_state.value = state;
     address_zip.value = zip_code;
   });
 });
});

If we make it past that conditional, we subscribe to the elements.us_autocompletion.selection event and update our state if it’s targeting the correct form. And just like that, our address forms have autocompletion and address verification.

Mailing a Postcard With JavaScript Part 2: Add Address Autocomplete & Address Verification image 3

Next, we prepare our payload and enable our users to submit their requests to the app’s backend. Place this in the CreatePostcard component:

async function handleSubmit() {
  const body = {
      toAddress: { ...toAddress.value },
      fromAddress: { ...fromAddress.value },
      frontTemplate: frontTemplate.value,
      backTemplate: backTemplate.value
  };
  error.value = "";
  success.value = false;
  loading.value = true;
 
  const response = await fetch("http://localhost:3030/postcard/create", {
      method: "POST",
      body: JSON.stringify(body),
      headers: {
          "Content-Type": "application/json"
      }
  });

  const data = await response.json();

  if (!data.success) {
      loading.value = false;
      error.value = data.error_message;
      return;
  }

  setTimeout(function(){
      loading.value = false;
      backThumbnail.value = data.postcard.thumbnails[1].medium;
      frontThumbnail.value = data.postcard.thumbnails[0].medium;
      success.value = true;
  }, 4000);
}

Note the use of .value to access the underlying value of the reference object while we’re inside our script tag. You will notice the setTimeout function that wraps the code path if the request is successful. This is because rendering thumbnails is an asynchronous task in Lob and depending on when you go to thumbnail link, the task may or may not have been completed. There's actually an webhook event that you could subscribe to called postcard.rendered_thumbnails that will let you know when the task is complete. Stay tuned for future tutorials where we will go over subscribing and ingesting events via webhooks.

We also have to add the submit button for our form, so after the “container” class we will add the following to the “CreatePostcard” component:

<div class="submit-area">
  <h2>Ready to go?</h2>
  <button class="btn_submit" @click="handleSubmit()">Submit</button>
</div>

Building a handler

We first need to enable our server to parse the JSON that we’ll be sending in our body on our backend. Express comes with an inbuilt helper function we can use for this, so in our backend/index.js file, we will use the JSON middleware. Add this after the line that has app.use(cors()):

app.use(express.json());

Now, we need to build the handler function. Before we start with the rest of the backend code, we need to install the Lob SDK via npm. In the terminal type following command (making sure, you are in the “backend” folder for the project):

npm install --save lob

Let’s create a new file at postcard/index.js. We will use the Lob SDK for Node.js to build our handler. We import the SDK then instantiate it with our API key. Add the following to postcard/create.js:

import L from "lob";

export default async function createPostcard(req, res) {
 const Lob = L(process.env.LOB_SECRET_API_KEY);
}

The following steps will fill in the createPostcard function. We use the Lob.postcards.create method to verify our addresses during that operation and queue our postcard for sending. This method takes two parameters: the options object, and a callback function.

We pass in our options, then in the callback function, we check if there is an error. We get helpful error messages back from the API, but they’re nested. We do some restructuring to make it easier for our front end to consume these messages. If there is no error, we return a success message and the newly created postcard object that was sent to us from the Lob API. We will use this object to show a preview of what the postcard will look like on the frontend. Place the following code inside the createPostcard function.

const { toAddress, fromAddress, frontTemplate, backTemplate, description } = req.body;

Lob.postcards.create(
 {
   description: description,
   to: toAddress,
   from: fromAddress,
   front: frontTemplate,
   back: backTemplate,
 },
 function (err, postcard) {
   if (err) {
     return res.status(err.status_code || 500).send({
       success: false,
       error_message:
         err?._response?.body?.error?.message ||
         err.message ||
         "Unknown error.",
     });
   } else {
     res.send({ success: true, postcard: postcard });
   }
 }
);

It’s possible to check the addresses separately at this stage if we’d prefer. The Lob.usVerifications.verify() method is powerful. The API takes a slightly different structure for the address argument so that it’ll need a little restructuring:

Lob.usVerifications.verify(
 {
   primary_line: toAddress.address_line1,
   city: toAddress.address_city,
   state: toAddress.address_state,
 },
 function (err, res) {
   if (err) reject(new Error(err));
   resolve(res);
 }
);

The response from the verification API is detailed and helpful. We get back a confidence score, a deliverability enum, and some deliverability analysis. This API doesn’t just give us a binary deliverable or undeliverable status. Instead, it summarizes the analysis into one of these values:

  • deliverable
  • deliverable_unnecessary_unit
  • deliverable_incorrect_unit
  • deliverable_missing_unit
  • undeliverable

You can switch on these values to update your CRM if it’s helpful for your sales team.

Now, back to our application. The last thing left to do is to add this handler to our router at backend/router.js.

import { Router } from "express";

import createTemplate from "./template/create.js";
import listTemplates from "./template/list.js";
import createPostcard from "./postcard/create.js";

const router = new Router();

router.post("/templates/create", createTemplate);
router.get("/templates", listTemplates);
router.post("/postcard/create", createPostcard);

export default router;

Next steps

We’ve set up a form to accept addresses in our app, verified addresses, and converted our bits into atoms. Our application can now trigger physical mail delivery to a customer anywhere in the US. That’s pretty cool!

You can review the project code before reading this series’s third and final article, where we’ll adjust our application to manage the postcards we’ve sent — including canceling them — and use webhooks to follow our postcard’s journey through the system.

Try Lob’s Print & Mail API for yourself now, or continue to article three to add mail management to our app.

Continue Reading