Maileditor Docs

React

A Vite React app with a small Express route that signs the user id.

A React single-page app cannot hold the Plugin Secret, so signing happens in a separate Express server. The React component fetches the signed values and renders the iframe. This guide uses Vite, but the component works the same in any React setup.

Before you start

Follow the first three steps of the quickstart: create a plugin, add localhost:5173 to its allowed domains, and copy the Plugin ID and Secret.

Configuration

.env
MAILEDITOR_PLUGIN_ID=9f3c1e7a-4b21-4c8e-9a77-1d2f5b6c8e10
MAILEDITOR_PLUGIN_SECRET=your_plugin_secret
MAILEDITOR_EDITOR_BASE_URL=https://maileditor.net
MAILEDITOR_PLUGIN_VERSION=1.0.0

Do not prefix these with VITE_. Vite would then inline the secret into the browser bundle.

Sign on the server

Install Express (npm install express). Vite projects already have "type": "module" in package.json, which the import syntax below needs.

server.js
import express from 'express'
import { createHmac } from 'node:crypto'

const app = express()

app.get('/api/auth', (req, res) => {
  const user = req.query.user
  if (typeof user !== 'string' || user === '') {
    return res.status(400).json({ error: 'user is required' })
  }

  const expires = Math.floor(Date.now() / 1000) + 300
  const payload = `v1|${Buffer.byteLength(user)}:${user}|${expires}`
  const signature = createHmac('sha256', process.env.MAILEDITOR_PLUGIN_SECRET)
    .update(payload)
    .digest('hex')

  res.json({
    externalUserId: user,
    identityExpires: expires,
    identitySignature: signature,
    pluginId: process.env.MAILEDITOR_PLUGIN_ID,
    pluginVersion: process.env.MAILEDITOR_PLUGIN_VERSION,
    editorBaseUrl: process.env.MAILEDITOR_EDITOR_BASE_URL,
  })
})

app.listen(3001)

Start it with node --env-file=.env server.js. In a real app, take the user id from the caller's session instead of trusting the user query parameter.

Proxy /api to the server so the page and the API share one origin during development:

vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  server: {
    proxy: { '/api': 'http://localhost:3001' },
  },
})

Render the iframe

src/Editor.tsx
import { useEffect, useState } from 'react'

export function Editor({ userId }: { userId: string }) {
  const [src, setSrc] = useState<string>()

  useEffect(() => {
    fetch(`/api/auth?user=${encodeURIComponent(userId)}`)
      .then((res) => res.json())
      .then((auth) => {
        const params = new URLSearchParams({
          external_user_id: auth.externalUserId,
          identity_expires: String(auth.identityExpires),
          identity_signature: auth.identitySignature,
          plugin_version: auth.pluginVersion,
        })
        setSrc(`${auth.editorBaseUrl}/plugin-editor/v1/${auth.pluginId}?${params}`)
      })
  }, [userId])

  if (!src) return null

  return (
    <iframe
      src={src}
      title="Email editor"
      allow="clipboard-write; fullscreen"
      style={{ width: '100%', height: '100vh', border: 0 }}
    />
  )
}

Use it anywhere: <Editor userId="customer-user-4471" />.

In production, serve the built dist folder from the same Express app or route /api to it from your reverse proxy, and add the production origin to the plugin's allowed domains.

Next steps

  • Add template_id to the query string to open one of the user's saved templates. See Embedding the editor.
  • If the iframe shows "Plugin editor unavailable", read result.error in the session response. See Error codes.

On this page