Skip to main content

SvelteKit

The editor in a SvelteKit app: one server route mints tokens, one page opens the editor with createEditor from @lettrove/embed. One process, four small files. Written for SvelteKit 3.

You need a project's two test keys from lettrove.com → Settings → Embed (step 1 of the Quickstart), and Node.js 22.12 or newer (what SvelteKit asks for).

1. Create the app and install two packages​

npx sv create lettrove-sveltekit --template minimal --types ts --no-add-ons --install npm
cd lettrove-sveltekit
npm install @lettrove/embed @lettrove/node

@lettrove/embed opens the editor, in the browser. @lettrove/node mints tokens with your secret key, on the server only.

2. Your secret key​

Create .env in the app's folder:

.env
LETTROVE_SECRET_KEY=lt_sk_test_paste_yours_here

SvelteKit reads only the variables you declare. Declare this one in src/env.ts; a variable not marked public is handed to server code only, so the key never reaches a browser.

src/env.ts
import { defineEnvVars } from '@sveltejs/kit/env';

export const variables = defineEnvVars({
LETTROVE_SECRET_KEY: {},
});

3. The token route​

The editor opens with a short-lived token. Create src/routes/lettrove-token/+server.ts:

src/routes/lettrove-token/+server.ts
import { json } from '@sveltejs/kit';
import { Lettrove } from '@lettrove/node';
import { LETTROVE_SECRET_KEY } from '$app/env/private';
import type { RequestHandler } from './$types';

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

export const POST: RequestHandler = async ({ request }) => {
// In your product: your own id for the signed-in person, from your session (locals).
const userId = 'demo-user';
const { token } = await lettrove.tokens.create({
user: { id: userId },
origin: request.headers.get('origin')!, // the page the editor opens on
});
return json({ token }, { headers: { 'Cache-Control': 'no-store' } });
};

4. The page​

Replace src/routes/+page.svelte. Put your publishable key where it says so.

src/routes/+page.svelte
<script lang="ts">
import { onMount } from 'svelte';
import { createEditor, type EditorHandle } from '@lettrove/embed';

let container: HTMLDivElement;
let editor: EditorHandle | null = null;

async function getToken() {
const response = await fetch('/lettrove-token', { method: 'POST' });
return (await response.json()).token;
}

onMount(() => {
createEditor({
container,
publishableKey: 'lt_pk_test_paste_yours_here',
getToken,
on: { 'design:saved': ({ designId }) => console.log('saved', designId) },
}).then((opened) => (editor = opened));
return () => editor?.destroy();
});

async function exportHtml() {
const email = await editor?.exportHtml();
console.log(email?.html);
}
</script>

<div class="page">
<button onclick={exportHtml}>Export HTML</button>
<div bind:this={container} class="editor"></div>
</div>

<style>
:global(body) { margin: 0; }
.page { height: 100vh; display: flex; flex-direction: column; }
.editor { flex: 1; }
</style>

5. Run it​

npm run dev

Open the address it prints (http://localhost:5173). Add a Heading from the panel on the left; the browser's console prints saved and the design's id. Keep that id to open the same design again: pass it as designId. Click Export HTML and the console prints the whole email, compiled for every inbox.

Next​