Skip to main content

Keeping your own copy

Lettrove keeps your users' designs. Many products also want each design in their own database: for backups, for reporting, to render it on their own schedule, or to leave nothing behind if they move. Two ways, depending on where your code runs.

Three steps, and every one starts at your end:

  1. Lettrove sends a signed webhook to your server: "design X of your user Y was saved". It carries ids only, never the design.
  2. Your server asks the Lettrove API for that design, with your secret key. It only ever gets designs from your own project.
  3. Your server writes it into your own database.

Lettrove never connects to your database, and you never connect to ours: everything passes through the webhook and the API.

How and where you store it is yours: any database, any shape. Two things keep a copy right: store the design document as it is, and keep only the newest revision, because a delivery can arrive late.

import { Lettrove, verifyWebhook } from '@lettrove/node';

const lettrove = new Lettrove({ secretKey: process.env.LETTROVE_SECRET_KEY });

app.post('/lettrove/webhooks', express.text({ type: 'application/json' }), async (req, res) => {
const event = await verifyWebhook(req.body, req.get('lettrove-signature'), process.env.LETTROVE_WEBHOOK_SECRET);

if (event.type === 'design.saved') {
const { designId, userId } = event.data;
const design = await lettrove.designs.get(designId, { user: { id: userId } });
// Yours: store design.doc under design.id, unless you already hold a newer design.revision.
await saveDesignCopy(design);
}
if (event.type === 'design.deleted') {
await deleteDesignCopy(event.data.designId); // yours
}

res.sendStatus(200);
});

design is { id, name, mode, revision, createdAt, updatedAt, doc }; doc is the design document (The design document).

This works whether or not the editor is open, sees every user's saves, and survives your page being closed mid-edit.

To copy what you already have, page through each person's designs once:

let cursor = null;
do {
const page = await lettrove.designs.list({ user: { id: userId }, limit: 200, ...(cursor ? { cursor } : {}) });
for (const summary of page.designs) {
const design = await lettrove.designs.get(summary.id, { user: { id: userId } });
await saveCopy(design);
}
cursor = page.nextCursor;
} while (cursor);

From the page, as your user saves​

editor.on('design:saved', async () => {
const { designId, revision, design } = await editor.getDesign();
await fetch('/my-api/designs', {
method: 'PUT',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ designId, revision, design }),
});
});

Simpler, but it only runs while the editor is open in that page.

Using your copy​

The design document is yours to store as it is. Hand it back to Lettrove to render it, with nothing stored on Lettrove's side:

const email = await lettrove.render({ doc: copy.doc, designId: copy.id, merge: { contact: { first_name: 'Ada' } } });
// email.html, email.text, email.subject… — the same output as an export

Treat the design as a sealed box: store it, return it, never change it by hand. Its format belongs to Lettrove and can gain fields between releases; a design saved by any 1.x editor always opens and renders in every later one. See The design document.