跳到正文
DEV Community· Mailkaro·· 3 小時前AI 評分46

如何在 React app 加入拖放式電郵編輯器:完整教學

Add a drag and drop email builder to your React app (full tutorial)

AI 導讀

教學示範如何在 Vite + React app 加入 Mailkaro 拖放式電郵編輯器,並逐步設定帳戶、API token 和 React 元件。內容涵蓋儲存及重新載入設計、合併標籤、在伺服器產生 HTML,以及設定用戶權限、S3 儲存和 AI 助手。教學亦説明免費方案涵蓋編輯器,Pro 提供儲存和 AI 等功能,首月免費且毋須信用卡。

正文

Your users want to design their own emails. You don't want to spend months building an editor.

In this post we add a complete drag and drop email builder to a fresh Vite + React app, step by step. It's the written version of a 9 minute video where every step is filmed for real.

🎥 Watch the video: https://www.linkedin.com/feed/update/urn:li:activity:7519114078284562432

What you need: a React app (ours is a fresh Vite app) and Node 18 or newer.

1. Create an account and a token

Sign up at mailkaro.com. Sign in is passwordless: every sign in uses a one time code sent to your email, and an account can only be signed in on one device at a time.

Then open Embed & API, click Generate new token, and choose which features the token exposes, for example:

  • Save, export and history
  • Uploads and assets
  • AI Assistant

You get two keys:

Key Starts with Where it goes
Publishable pk_live_ your frontend
Secret sk_live_ your server only, never in frontend code

2. Install and render

npm install react-email-craft

Put the publishable key in .env.local:

VITE_MAILKARO_KEY=pk_live_...

Then render the editor:

import EmailEditor from "react-email-craft";

export default function App() {
  return (
    <EmailEditor
      token={import.meta.env.VITE_MAILKARO_KEY}
      style={{ height: "100vh" }}
    />
  );
}

That's the full editor: the canvas in the middle, blocks and settings on the right.

Tip: Vite's starter index.css centres everything in a narrow box. Replace it with body { margin: 0; } so the editor gets the whole page.

Next.js: add "use client" at the top of the file, or import the editor with ssr: false.

3. Save and reopen a design

Add a ref and two buttons. exportHtml hands you the design (JSON describing everything on the canvas) and the HTML. Store the design: that's what reopens the email later.

import { useRef } from "react";
import EmailEditor from "react-email-craft";

export default function App() {
  const ref = useRef(null);

  const save = () =>
    ref.current.editor.exportHtml(({ design, html }) => {
      localStorage.setItem("design", JSON.stringify(design)); // your database in a real app
    });

  const open = () => {
    const saved = localStorage.getItem("design");
    if (saved) ref.current.editor.loadDesign(JSON.parse(saved));
  };

  return (
    <>
      <button onClick={save}>Save</button>
      <button onClick={open}>Open</button>
      <EmailEditor ref={ref} token={import.meta.env.VITE_MAILKARO_KEY} style={{ height: "90vh" }} />
    </>
  );
}

4. Merge tags

Each merge tag has a label, the value your email service fills in, and a sample:

<EmailEditor
  ref={ref}
  token={import.meta.env.VITE_MAILKARO_KEY}
  mergeTags={{
    first_name: { label: "First name", value: "{{first_name}}", sample: "Alex" },
    company: { label: "Company", value: "{{company}}", sample: "Acme" },
  }}
/>

In a text block, your users pick Insert merge tag. The tag lands as a labelled pill, so they can see exactly where each field goes.

5. Render email-safe HTML on your server

Sending needs finished HTML, and you can make it on your own server from the saved design:

npm install email-craft-sdk express
import express from "express";
import { renderDesign, renderMjml } from "email-craft-sdk";

const app = express();
app.use(express.json({ limit: "2mb" }));

app.post("/render", (req, res) => {
  const html = renderDesign(req.body.design, { fullDocument: true });
  const mjml = renderMjml(req.body.design);
  res.json({ html, mjml });
});

app.listen(3001, () => console.log("Server on http://localhost:3001"));

There's no headless browser, no network call, and no key needed. It runs inside your backend.

6. Decide what each user can do

The plan unlocks when both keys work together: the publishable key in the browser, and the secret key on your server. Add one route:

import { Mailkaro } from "email-craft-sdk";

const mailkaro = Mailkaro.fromEnv(); // reads MAILKARO_SERVER_KEY (sk_live_...)

app.get("/mailkaro/embed-runtime", async (_req, res) => {
  res.json(await mailkaro.resolveRuntime());
});

Point the editor at it with runtimeUrl:

<EmailEditor
  ref={ref}
  token={import.meta.env.VITE_MAILKARO_KEY}
  runtimeUrl="/mailkaro/embed-runtime"
  /* ...mergeTags, style */
/>

In development, let Vite forward these routes to Node, so they run on the same origin as your app:

export default defineConfig({
  plugins: [react()],
  server: {
    proxy: { "/mailkaro": "http://localhost:3001", "/render": "http://localhost:3001" },
  },
});

Your server answers, and the secret key never leaves it.

7. Your own storage and your own AI

Add your S3 bucket and AI provider key to the server's .env, then three small routes:

import multer from "multer";

app.post("/mailkaro/ai", async (req, res) => {
  res.json(await mailkaro.ai(req.body.method, req.body.input));
});

const upload = multer();
app.post("/mailkaro/upload", upload.single("file"), async (req, res) => {
  res.json(await mailkaro.uploadAsset({
    buffer: req.file.buffer,
    filename: req.file.originalname,
    contentType: req.file.mimetype,
    scopeId: "demo", // your tenant or workspace id
  }));
});

app.get("/mailkaro/assets", async (_req, res) => {
  res.json(await mailkaro.listAssets({ scopeId: "demo" }));
});

Now the image library has an Amazon S3 tab with your bucket's images, and new uploads go straight into it. The AI assistant runs on your own key and builds emails right on the canvas. The keys stay on the server, and the browser never sees them.

The whole loop

  • One token
  • One component
  • Two calls to save and reopen
  • One function to render on your server
  • One route that decides what each user can do

The free plan covers the editor itself. Pro unlocks storage, AI and more, and the first month is free with no card.

Next in the series: the same setup in Angular, Vue and plain JavaScript.

來源:DEV Community · dev.to