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 1: Creating a Postcard With Node.js
Engineering
November 11, 2021

Mailing a Postcard With JavaScript Part 1: Creating a Postcard With Node.js

Share this post

Lob’s Print & Mail and Address Verification APIs enable developers to interact with Lob’s services programmatically. You can mail a postcard or letter to your customers at critical points in their customer journey as easily as you send an email. These physical mail pieces help you stay top of mind with customers to help increase customer lifetime value and drive bottom line revenue.

In this three-part tutorial, we’ll create a postcard template, verify our recipient’s address, send a postcard, and track it along its journey. We’ll create a Javascript application to do all this, so our users can access everything they need in one place.

To follow along, you’ll need your own Lob account. You can sign up here and find your API keys in your settings. Take a note of the secret and the publishable API keys. We’ll use the publishable key any time we interact with Lob from the frontend and the secret key anywhere we access the Lob API from the backend.

Lob direct mail account settings

Lob’s APIs are well documented, and we can choose from SDKs in various languages. We’ll focus on Node.js in this series, but the approach will work in whichever language you choose.

Our template creation app

Our app comprises two parts: a Vue frontend and a Node backend. In this part of our tutorial, we’ll enable our users to create postcard templates that they can later use to send physical postcards to their customers.

Our users will create the template with HTML and CSS then store it on the Lob server. This template has the layout and text ready to send to all our user’s customers. Once we submit these templates to Lob, we can use them as many times as we’d like. We could send hundreds — or even thousands — of postcards from a single template.

Let’s start creating our application by giving our users the ability to build and submit their own templates. In this tutorial, we’ll use one of Lob’s example postcard templates and allow our users to change the background picture and text.

Creating the application’s Vue front end

First, let’s instantiate a new Vue application using Vite:

npm init vite@latest

Let’s name our project and select Vue. We won’t use TypeScript.

Vue front-end screenshot

We follow the instructions Vite displays on our screen to install the dependencies and get the starter site up and running.

starter site

Point your browser to localhost:3000 to see the boilerplate app.

Vue + Vite starter site

Before we start creating our application, create a file called .env to hold our environment variables. The Vite framework exposes environment variables that have a “VITE_” prefix. For more information on this, read the Vite documentation. As a developer, you never want to make a commit to Github that contains sensitive login information.

Save your .env file in the root folder.

VITE_CLOUDINARY_NAME=<insert_name>
VITE_CLOUDINARY_PRESET=<insert_preset>

Now, let’s create a new component for our front template, Front.vue, and add the template and styling based on one of Lob’s examples. Specifically, we look at the front of the Product Promotion postcard. We will replace the default HelloWorld component with the new Front component in the App.vue file.

src/App.vue

// src/App.vue

<script setup>
import FrontVue from './components/Front.vue';
</script>

<template>
<FrontVue />
</template>

<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

src/components/Front.vue

<template>
 <div
   class="body"
   :style="{ backgroundImage: `url(https://s3.us-west-2.amazonaws.com/public.lob.com/solutions/Lob_demo_postcard_conversion/Retail+psc/4x6+Retail+Postcard/Links/indoor-4148898.jpg)` }"
 >
   <div class="header">Love the home you live in</div>
   <div class="logo">virtonis</div>
   <div id="safe-area"></div>
 </div>
</template>

<style scoped>
 .body {
   position: absolute;
   width: 6.25in;
   height: 4.25in;
   background-size: 6.25in 4.25in;
   background-repeat: no-repeat;
 }

 .header {
   position: absolute;
   width: 4.3232in;
   height: 1.8625in;
   left: 0.9138in;
   top: 0.2847in;
   font-size: 39.247pt;
   line-height: 35.804pt;
   font-family: "Lato-Light";
 }

 .logo {
   position: absolute;
   height: 0.4407in;
   right: 0.2in;
   top: 3.7072in;
   font-size: 24.787pt;
   line-height: 29.745pt;
   font-family: "Lato-Regular";
 }

 #safe-area {
   position: absolute;
   width: 5.875in;
   height: 3.875in;
   left: 0.1875in;
   top: 0.1875in;
   background-color: rgba(255, 255, 255, 0.5);
 }
</style>

Mailing a Postcard With JavaScript Part 1: Creating a Postcard With Node.js image 2

We want to allow our users to change each of these elements. We’ll use the Vue composition API to help us do that.

We add a <script setup> tag to our component and set up some reactive variables. We then set the default values to those the template already uses, so nothing changes on the frontend when we update the template.

// src/components/Front.vue

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

const imgSrc = ref("https://s3.us-west-2.amazonaws.com/public.lob.com/solutions/Lob_demo_postcard_conversion/Retail+psc/4x6+Retail+Postcard/Links/indoor-4148898.jpg");
const headerText = ref("Love the home you live in")
const logoText = ref("virtonis")
</script>

<template>
 <div class="body" :style="{ backgroundImage: `url(${imgSrc})` }">
   <div class="header">{{ headerText }}</div>
   <div class="logo">{{ logoText }}</div>
   <div id="safe-area"></div>
 </div>
</template>

Now that we have reactive values, we need to give our users some way to interact with those values. We use the v-model to create a two-way binding between the input and the reactive value for the header and logo text. As we type into these fields, we’ll be able to see the form updating.

// src/components/Front.vue
<template>
  <div class="body" :style="{ backgroundImage: `url(${imgSrc})` }">
      <div class="header">{{ headerText }}</div>
      <div class="logo">{{ logoText }}</div>
      <div id="safe-area"></div>

      <div class="controls">
        <form @submit.prevent>
           <h2>Edit your template</h2>
           <div class="form-field">
              <label for="headerText">Header text:</label>
              <textarea id="headerText" v-model="headerText" />
           </div>
           <div class="form-field">
              <label for="logoText">Logo text:</label>
              <input id="logoText" v-model="logoText" />
           </div>
        </form>
     </div>
  </div>
</template>

<style scoped>
.controls {
  position: absolute;
  top: 4.5in;
}

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

label {
  text-align: left;
  margin-bottom: 8px;
  margin-top: 8px;
}
</style>


Mailing a Postcard With JavaScript Part 1: Creating a Postcard With Node.js image 3

We then upload the image to a third-party service, like Cloudinary. Cloudinary has a helpful library that provides the upload modal, handles the cloud storage, and provides a URL we can pass into our template.

We first need to add the script import for Cloudinary to our main index.html file right above the “main.js” script tag that holds our Vue app:

<script
     src="https://widget.cloudinary.com/v2.0/global/all.js"
     type="text/javascript"
   ></script>

When we instantiate the Cloudinary script, it adds a cloudinary library with an openUploadWidget to our window object.

Let’s create a handler function to open the widget and update our state when the widget completes. To follow along, sign up for Cloudinary to get your cloud name and upload preset. Put these values in the .env file we created earlier.

// src/components/Front.vue

function openUploadModal() {
 window.cloudinary.openUploadWidget(
   {
     cloud_name: import.meta.env.VITE_CLOUDINARY_NAME,  
     upload_preset: import.meta.env.VITE_CLOUDINARY_PRESET
   },
   (error, result) => {
     if (!error && result && result.event === "success") {
       imgSrc.value = result.info.url
     }
   }
 );
}

Next, we add a button to our template that will trigger this widget when the user clicks.

// src/components/Front.vue
<form @submit.prevent>
   <div class="form-field">
       <label for="backgroundImage">Background Image:</label>
       <button @click="openUploadModal()">Upload files</button>
   </div>
</form>

The next task we need to tackle is to bring some routing to our Vue app. After saving the postcard template, we want the app to redirect to another page that will list all of the templates that we have saved.  

Add the vue-router package to our project by running the following command: “npm install --save vue-router@4”. Create a new file under src/router/index.js and add the following:

// src/router/index.js

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

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

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

export default router;

Since we are missing the ListTemplates component, let’s create a stub of this for this time being.

// src/components/ListTemplates.vue

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

const templates = reactive({});

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

<template>
<p>We will show the list of templates here</p>
</template>

The last step we need to do is put a reference to the router in the main.js file and update the App.vue component to use the router.

src/main.js

// src/main.js

import { createApp } from 'vue'
import App from './App.vue'
import router from "./router";

createApp(App).use(router).mount('#app')

src/App.vue

// src/App.vue

<script setup>
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
</script>

<template>
<router-view></router-view>
</template>

<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

Now, we add our template name. Then, we send the template information to our backend.

// src/components/Front.vue

<script setup>
import router from "../router";

const templateName = ref("")

async function submitTemplate() {
await fetch(`http://localhost:3030/templates/create?logoText=${logoText.value}&templateName=${templateName.value}&backgroundImage=${imgSrc.value}&description=${headerText.value}`, { method: "POST" })
router.push("/list")
}
</script>

<template>
<div>
<h2>Finished with your postcard design?</h2>
<div class="form-field">
<label for="templateName">Give it a name</label>
<input id="templateName" v-model="templateName" />
</div>
<button @click="submitTemplate()">Upload template</button>
</div>
</template>

Let’s next hop over to the backend and get our route ready to receive this information.

Creating the application’s Node backend

To create our back end, we will create a new folder called “backend”. After changing into this directory, we will create a package.json file with the following contents:

// package.json

{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
  "dev": "nodemon index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
  "cors": "^2.8.5",
  "dotenv": "^10.0.0",
  "express": "^4.17.1",
  "node-fetch": "^3.0.0",
  "nodemon": "^2.0.13"
}
}

This package.json file lists our dependencies --  express web framework to structure our app; dotenv to keep our environment variables secret; cors to handle data sent to our front end; and nodemon to restart our server every time we save. We added "type": "module" to our package.json to use esm import and export. Run the command “npm install” to install all of our dependencies.

Let’s next create an index.js file and add a basic web server setup. We also make and import router.js to organize our routes.
index.js

// index.js
import express from "express";
import cors from "cors";
import router from "./router.js";

import dotenv from "dotenv";
dotenv.config();

const app = express();
const port = process.env.PORT || 3030;

app.use(cors());

app.use("/", router);

app.listen(port, () => {
 console.log(`Listening on port ${port}.`);
});

router.js

// router.js
import { Router } from "express";

import createTemplate from "./template/create.js";

const router = new Router();

router.post("/templates/create", createTemplate);

export default router;

After we set this up, we will need to create a .env file that will hold our Lob API test key.

// .env

LOB_SECRET_API_KEY=<place your key here>

We’ll be sending the variables as query parameters from Vue to our backend. We have the replicated template on the back end and populate it with the user’s front-end data.

// template/create.js

export default async function createTemplateHandler(req, res) {
 try {
   const htmlString = createHTMLTemplate(req.query);
 } catch (error) {
   res.send(error);
 }
}

function createHTMLTemplate({ logoText, description, backgroundImage }) {
 return `
 <html>
    <head>
    <meta charset="UTF-8">
    <link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet" type="text/css">
    <title>4x6 Postcard Front</title>
    <style>
        *, *:before, *:after {
            -webkit-box-sizing: border-box;
            -moz-box-sizing: border-box;
            box-sizing: border-box;
        }
   
        @font-face {
            font-family: "Lato-Bold";
            src: url(https://s3.us-west-2.amazonaws.com/public.lob.com/solutions/Lob_demo_postcard_conversion/Retail+psc/4x6+Retail+Postcard/Document+fonts/Lato-Bold.ttf) format("truetype");
        }
   
        @font-face {
            font-family: "Lato-Light";
            src: url(https://s3.us-west-2.amazonaws.com/public.lob.com/solutions/Lob_demo_postcard_conversion/Retail+psc/4x6+Retail+Postcard/Document+fonts/Lato-Light.ttf) format("truetype");
        }
   
        @font-face {
            font-family: "Lato-Regular";
            src: url(https://s3.us-west-2.amazonaws.com/public.lob.com/solutions/Lob_demo_postcard_conversion/Retail+psc/4x6+Retail+Postcard/Document+fonts/Lato-Regular.ttf) format("truetype");
        }
   
        body {
            width: 6.25in;
            height: 4.25in;
            margin: 0;
            padding: 0;
            background-image: url(${backgroundImage});
            background-size: 6.25in 4.25in;
            background-repeat: no-repeat;
        }
   
        .header {
            position: absolute;
            width: 4.3232in;
            height: 1.8625in;
            left: 0.9138in;
            top: 0.2847in;
            font-size: 39.247pt;
            line-height: 35.804pt;
            font-family: "Lato-Light";
        }
       
        .logo {
            position: absolute;
            height: .4407in;
            right: 0.2in;
            top: 3.7072in;
            font-size: 24.787pt;
            line-height: 29.745pt;
            font-family: "Lato-Regular";
        }
   
        #safe-area {
            position: absolute;
            width: 5.875in;
            height: 3.875in;
            left: 0.1875in;
            top: 0.1875in;
            background-color: rgba(255, 255, 255, 0.5);
        }
   
    </style>
    </head>
   
    <body>
        <div class="header">${description}</div>
        <div class="logo">${logoText}</div>
        <div id="safe-area"></div>
    </body>
   
    </html>
    `;
}


We’re effectively adding dynamic values to a large template literal. We’ll use node-fetch, a Node implementation of the browser fetch API, to send our data to Lob. We need to encode the data and identify ourselves with the API correctly. Let’s modify the createTemplateHandler function to add the call to Lob API.

// template/create.js
import fetch from "node-fetch";

export default async function createTemplateHandler(req, res) {
  try {
      const htmlString = createHTMLTemplate(req.query);
      const params = new URLSearchParams();
      const templateName = req.query.templateName || new Date().toString();

      params.append("description", templateName);
      params.append("html", htmlString);

      await fetch("https://api.lob.com/v1/templates", {
          method: "POST",
          body: params,
          headers: {
              Authorization: `Basic ${Buffer.from(
              process.env.LOB_SECRET_API_KEY + ":"
              ).toString("base64")}`,
          },
      });

      res.send({ message: "Template created." });
  } catch (error) {
      res.send(error);
  }
}

To keep the third-party packages to a minimum, we use Node’s UrlSearchParams rather than a package such as form-data. UrlSearchParams also sets the headers we need automatically.

We append our description and HTML parameters to the data we send to Lob, then prepare our headers. We use a basic username and password to authenticate ourselves with the Lob API. The username should be our API key, which we get from the environment variable LOB_SECRET_API_KEY, and the password should be blank. This configuration is the same as setting an Authorization header, as the code above shows.

Once our authentication is successful, we send a message back to our Vue application to let it know we’re done.

res.send({ message: "Template created." });

Now that we’ve created the template, we make a route to list our templates and consume the route on the frontend. In Node, we use a straightforward GET request using node-fetch:

// templates/list.js

import fetch from "node-fetch";

export default async function listTemplate(req, res) {
 const response = await fetch("https://api.lob.com/v1/templates", {
   headers: {
     Authorization: `Basic ${Buffer.from(
       process.env.LOB_SECRET_API_KEY + ":"
     ).toString("base64")}`,
   },
 });

 const templates = await response.json();
 res.send(templates.data);
}

Now that we have the functionality to grab our saved templates from the Lob API, let’s add that endpoint to the Express app.

// router.js

import { Router } from "express";
import createTemplate from "./template/create.js";
import listTemplate from "./template/list.js";

const router = new Router();
router.post("/templates/create", createTemplate);
router.get("/templates", listTemplate);

export default router;

We authenticate ourselves in the same way, then pass that data on to our clients. We want to get that data in Vue and display it to our users. We fetch and process the data using the onMounted function. We then update our reactive value, which triggers our template to rerender. So let’s update the ListTemplates component that we stubbed out earlier.

// src/components/ListTemplates.vue

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

const templates = reactive({});

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

<template>
<ul class="flex">
<li v-for="template in templates.value">
<h3>{{ template.description }}</h3>
<p>Created on {{ template.date_created }}.</p>
          <p><a :href="'https://dashboard.lob.com/#/templates/' + template.id" target="_blank">Preview</a></p>
</li>
</ul>
</template>

<style scoped>
  li {
      display: block;
  }
  .template-frame {
      width: 200px;
      height:300px;
      overflow: hidden;
  }
</style>

Using the v-for directive, we iterate over the templates and destructure the more relevant values.

Mailing a Postcard With JavaScript Part 1: Creating a Postcard With Node.js image 4

Next steps

In this part of the tutorial, we’ve built our application to enable users to create and view templates in Lob. We have the project code saved here for you to review as you carry on to the next part of this tutorial. Next time, we’ll use these templates to send our real-life postcards, changing bits to atoms.

A well-designed postcard can enhance the relationship between your customers and your brand. Try Lob’s Print & Mail API yourself now, or continue to the second part of this tutorial to learn how to verify an address before sending a postcard.

Continue Reading