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

# Plain HTML

The editor on a page with no bundler and no framework: one script tag. Two files in all, served by
one small Express server that also mints the editor's tokens.

You need a project's two **test** keys from [lettrove.com](https://lettrove.com) → **Settings →
Embed** (step 1 of the [Quickstart](/docs/get-started/quickstart#1-create-a-project-and-copy-its-test-keys)),
and [Node.js](https://nodejs.org) 20.19 or newer.

## 1. Make a folder and install two packages

```bash
mkdir lettrove-html
cd lettrove-html
npm init -y
npm install express @lettrove/node
```

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

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

## 2. The server: one file

The editor opens with a short-lived **token**. Your server mints it with the secret key, so the
secret key never reaches a browser. Create `server.mjs`:

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

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

// Serves public/index.html, the page with the editor.
app.use(express.static('public'));

// 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('Open http://localhost:3000'));
```

## 3. The page: one file

Create a folder called `public`, and in it `index.html`. Replace `lt_pk_test_paste_yours_here` with
your publishable key.

```html title="public/index.html"
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>My email editor</title>
    <style>
      body { margin: 0; }
      #editor { height: 100vh; } /* the editor fills its box, so give the box a height */
    </style>
  </head>
  <body>
    <div id="editor"></div>

    <script src="https://cdn.jsdelivr.net/npm/@lettrove/embed@1/dist/lettrove.iife.js"></script>
    <script>
      async function getToken() {
        const response = await fetch('/lettrove-token', { method: 'POST' });
        return (await response.json()).token;
      }

      Lettrove.createEditor({
        container: '#editor',
        publishableKey: 'lt_pk_test_paste_yours_here',
        getToken,
        on: {
          'design:saved': ({ designId }) => console.log('saved', designId),
        },
      }).then((editor) => {
        window.editor = editor; // so you can try it from the browser console
      });
    </script>
  </body>
</html>
```

`@1` follows the newest 1.x of the loader. To pin an exact version, write it in full
(`@lettrove/embed@1.0.0`).

## 4. Run it

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

Open [http://localhost:3000](http://localhost:3000). 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 and pass it back as `designId` to open the same design again.

## 5. Get the email out

In the same console, type:

```js
const email = await editor.exportHtml();
email.html;
```

That is the whole email as HTML, compiled for Gmail, Outlook and Apple Mail, ready to send with any
email provider. `email.subject` is its subject, and `email.mergeTags` lists tags such as
`{{unsubscribe_url}}` for you to fill before sending ([Merge tags](/docs/editor/merge-tags)).

## Next

- If the box shows a code instead of the editor: [the Quickstart's checklist](/docs/get-started/quickstart#if-something-is-not-right).
- [`@lettrove/embed`](/docs/api/embed): every option and method of `createEditor`.
- [Getting designs out](/docs/editor/exports) · [Theming](/docs/editor/theming) · [Test and live keys](/docs/going-live/test-and-live).
