> Lettrove docs 1.x · https://docs.lettrove.com/docs/get-started/quickstart

# Quickstart

By the end of this page the Lettrove editor is open in a React app on your machine, a design is
saved, and you have its HTML. Everything below runs exactly as written.

**Not using React?** There is a page each for [Next.js](/docs/get-started/nextjs),
[Vue](/docs/get-started/vue), [Angular](/docs/get-started/angular),
[SvelteKit](/docs/get-started/sveltekit) and [plain HTML](/docs/get-started/html) (a script tag, no
build step). Each is complete on its own.

**You need:** a Lettrove account and [Node.js](https://nodejs.org) 20.19 or newer (check with
`node --version`).

## 1. Create a project and copy its test keys

1. Sign in at [lettrove.com](https://lettrove.com) and open **Settings → Embed**.
2. Type a name for your project (if you already have one, click **New project** first), and under
   **What your users make** choose **Emails**. Click **Make project**.
3. You now see two **test** keys. Copy both somewhere safe, then click **I have stored it safely**:
   - the **secret key**, `lt_sk_test_…`. It is shown **once**. It belongs on your server only.
   - the **publishable key**, `lt_pk_test_…`. It is public; it goes in your page.

Test keys open the editor on `localhost` with no other setup, and they are never billed.

## 2. The token server

The editor opens with a short-lived **token**. Your server mints it with the secret key, so the
secret key never reaches a browser. This is one small Express server; in your product it is one
route in the server you already have.

```bash
mkdir -p lettrove-quickstart/server
cd lettrove-quickstart/server
npm init -y
npm install express @lettrove/node
```

Create a file called `.env` with your secret key in it:

```bash title="server/.env"
LETTROVE_SECRET_KEY=lt_sk_test_paste_yours_here
```

Then the server itself:

```js title="server/server.mjs"
import express from 'express';
import { Lettrove } from '@lettrove/node';

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

// The page asks this route for a token whenever the editor needs one.
app.post('/lettrove-token', async (req, res) => {
  // In your product: your own id for the signed-in person, from your session.
  const userId = 'demo-user';
  try {
    const { token } = await lettrove.tokens.create({
      user: { id: userId },
      origin: req.get('origin'), // the page the editor opens on
    });
    res.set('Cache-Control', 'no-store').json({ token });
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: error.message, code: error.code });
  }
});

app.listen(3000, () => console.log('Token server on http://localhost:3000'));
```

Start it, and leave it running:

```bash
node --env-file=.env server.mjs
```

## 3. The React app

In a second terminal, from the `lettrove-quickstart` folder:

```bash
npm create vite@latest app -- --template react-ts
cd app
npm install
npm install @lettrove/react
```

Send the app's `/lettrove-token` requests to the token server:

```ts title="app/vite.config.ts"
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  server: { proxy: { '/lettrove-token': 'http://localhost:3000' } },
});
```

Replace `src/index.css` so the editor can fill the window:

```css title="app/src/index.css"
html, body, #root { margin: 0; height: 100%; }
```

And replace `src/App.tsx`. Put your publishable key where it says so.

```tsx title="app/src/App.tsx"
import { useRef } from 'react';
import { LettroveEditor, type EditorHandle } from '@lettrove/react';

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

export default function App() {
  const editor = useRef<EditorHandle | null>(null);

  async function exportHtml() {
    const email = await editor.current?.exportHtml();
    console.log(email?.html);
  }

  return (
    <div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
      <button onClick={exportHtml}>Export HTML</button>
      <LettroveEditor
        ref={editor}
        publishableKey="lt_pk_test_paste_yours_here"
        getToken={getToken}
        onDesignSaved={({ designId }) => console.log('saved', designId)}
        style={{ flex: 1 }}
      />
    </div>
  );
}
```

## 4. Run it

```bash
npm run dev
```

Open the address it prints ([http://localhost:5173](http://localhost:5173)). The editor loads in
the page.

1. In the panel on the left, click **Heading** (or drag it onto the email). A heading appears; click
   it to type your own words.
2. Open your browser's console (in Chrome: **View → Developer → JavaScript Console**). Within a
   moment of your edit it shows:

   ```text
   saved 01J9ZQ3W8D2K7M5T1V4XG6HB0R
   ```

That id is the design. Lettrove saves as your user works; in your product you store the id with your
own record (an email campaign, say) and pass it back as `designId` to open the same design again.

## 5. Get the email out

Click **Export HTML**. The console prints the whole email as HTML, compiled for Gmail, Outlook and
Apple Mail, ready to send with any email provider. The same call gives you `email.subject`, the
plain-text version in `email.text`, and `email.mergeTags`: tags such as `{{unsubscribe_url}}` for
you to fill before sending ([Merge tags](/docs/editor/merge-tags)).

Your server can get the same email later, with the editor closed:

```js
const email = await lettrove.designs.export(designId, { user: { id: 'demo-user' } });
```

**You are done.** Everything from here is making it yours.

## If something is not right

The editor never shows an empty box: if it cannot open, the box says why, with a code. When the
box says `token_invalid`, your token route failed — the terminal running the token server prints
the real reason. The usual first-day causes:

| Where | Code | Why | Fix |
|---|---|---|---|
| Terminal | `key_invalid` | The secret key in `.env` is wrong, has a space or quote around it, or was revoked | Copy it again, or make a new one in Settings → Embed |
| Terminal | `origin_not_allowed` | A **live** secret key on localhost | Use the **test** keys while you build |
| Terminal | ``Error: `secretKey` is required`` | `.env` was not read | Start the server with `node --env-file=.env server.mjs`, from the `server` folder |
| Terminal | `` `secretKey` is not a Lettrove secret key `` | A cut-off copy, or a space inside the key | Copy the whole key again |
| Editor box | `token_invalid` | Your token route did not answer with a token | Read the terminal: one of the lines above. If the terminal is quiet, the app is not reaching the server: check the `proxy` in `vite.config.ts` and that the server is running |
| Editor box | `frame_blocked` | The publishable key is wrong, or your page's Content-Security-Policy blocks the editor | Check the key; then [Allowed sites and CSP](/docs/editor/allowed-sites) |

Every code is explained in [Troubleshooting](/docs/going-live/troubleshooting).

## Where next

- [How it fits together](/docs/get-started/concepts): projects, keys, tokens, sites and end users.
- [`@lettrove/react`](/docs/api/react): every prop, and the editor handle.
- [Getting designs out](/docs/editor/exports): HTML, plain text, PNG, PDF and ZIP.
- [Options](/docs/editor/options) and [Theming](/docs/editor/theming): make it look like your product.
- [Test and live keys](/docs/going-live/test-and-live): before your users see it.
