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.
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:
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.
onMounted
lifecycle function to fetch the templates when our page first loads so our users can select from templates they have stored in Lob.
LOADING...
{{ error }}
Postcard Created!
![]()
Front
![]()
Back
Select a template:
Address you're sending to
Address you're sending from
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;

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);
})
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
});
});
});
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. 
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 is actually a 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:
Ready to go?
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);
}
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});
}
})
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);
}
);
deliverable
deliverable_unnecessary_unit
deliverable_incorrect_unit
deliverable_missing_unit
undeliverable
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;
Finally, we let our users know that their postcard is on its way to the customer.
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.
If you’re interested in developing expert technical content that performs, let’s have a conversation today.