Lumail.io
DocsBlogChangelog
⌘K
IntroductionTutorialsAI IntegrationSDKAPI ReferenceIntegrationsFeaturesWorkflows

Tutorials

Create an API TokenSend Transactional Email with SMTPSend React Email Templates with LumailBuild a V0 Capture PageDynamic Promo Codes with Webhooks

AI Integration

Claude Code (Plugin)Codex (Plugin)ChatGPT PluginHermesSkills (CLI)OpenClawCursor (MCP)TypeScript SDK IntegrationsTools API (v2)Examples & Recipes

SDK

SDK IntroductionSDK ConfigurationSDK ResourcesSDK ToolsErrors and Retries

API Reference

API TokensTypeScript SDKCLIMCP ServerTools API (v2)Admin Tools APIRate LimitsSMTP EndpointPOSTSend Transactional EmailPOSTEmail Verification APIPOSTCreate SubscriberGETGet SubscriberPATCHUpdate SubscriberDELETEDelete SubscriberPOSTAdd Tags to SubscriberDELETERemove Tags from SubscriberPOSTTrack EventGETGet Subscriber EventsGETGet All TagsPOSTCreate a TagGETList All CampaignsPOSTCreate CampaignGETGet CampaignPATCHUpdate CampaignDELETEDelete CampaignPOSTSend CampaignSend Email in HTMLSend Email in MarkdownSend Email in TiptapGETList SubscribersGETGet Tag by ID or Name

Integrations

ClickFunnels IntegrationSystemIO Integration

Features

VariablesTag Action LinksSurveysContent Deliverability CheckerEmail Deliverability ScoreEmail Engagement ScoreSubscriber EventsRevenue TrackingEmail Sending Queue

Workflows

WorkflowWorkflow Getting StartedWorkflow TriggersWorkflow BranchingWorkflow A/B TestingWorkflow GoalsWorkflow Exit RulesWorkflow Publishing and VersionsWorkflow Test Runs and ResultsWait StepEmail StepAction StepWebhook StepWorkflow Groups

domains

Email DomainsWeb Domains

Send React Email Templates with Lumail

Build transactional email templates as React components, render them to HTML, and send them through the Lumail transactional email API.

React Email lets you write email templates as React components and render them to email-safe HTML. Lumail accepts that HTML directly through the transactional email API with contentType: "HTML", so React Email covers the template layer and Lumail covers sending, domain authentication, subscriber records, and delivery tracking.

When to Use This

Use React Email with Lumail for transactional email: receipts, password resets, magic links, invitations, order confirmations, and product notifications.

Campaigns are a different content pipeline: they accept MARKDOWN and MAILY, not HTML. Newsletters and broadcasts stay in Lumail campaigns and workflows so unsubscribes, segmentation, and campaign reporting remain correct.

Prerequisites

  1. A verified Lumail email domain, so your from address is allowed.
  2. A Lumail API token (starts with lum_). See Create API Token.
  3. A Node.js project that can render React on the server.

Step 1 - Install the Packages

pnpm add lumail @react-email/components @react-email/render

@react-email/components provides the email-safe primitives. @react-email/render turns a component into an HTML string. lumail is the TypeScript SDK.

To preview templates locally while you build them, add the React Email dev server:

pnpm add -D react-email

Step 2 - Write the Template

Create emails/order-confirmation.tsx:

import {
  Body,
  Button,
  Container,
  Head,
  Heading,
  Hr,
  Html,
  Link,
  Preview,
  Section,
  Text,
} from "@react-email/components";

type OrderConfirmationProps = {
  name: string;
  orderNumber: string;
  total: string;
  receiptUrl: string;
};

export default function OrderConfirmation({
  name,
  orderNumber,
  total,
  receiptUrl,
}: OrderConfirmationProps) {
  return (
    <Html>
      <Head />
      <Preview>Your order {orderNumber} is confirmed</Preview>
      <Body
        style={{ backgroundColor: "#f6f6f6", fontFamily: "Arial, sans-serif" }}
      >
        <Container
          style={{
            maxWidth: "600px",
            padding: "24px",
            backgroundColor: "#ffffff",
          }}
        >
          <Heading style={{ fontSize: "22px", color: "#111111" }}>
            Thanks for your order, {name}
          </Heading>
          <Text style={{ fontSize: "15px", color: "#333333" }}>
            Order <strong>{orderNumber}</strong> is confirmed. Total: {total}.
          </Text>
          <Section style={{ margin: "24px 0" }}>
            <Button
              href={receiptUrl}
              style={{
                backgroundColor: "#111111",
                color: "#ffffff",
                padding: "12px 20px",
                borderRadius: "6px",
              }}
            >
              View your receipt
            </Button>
          </Section>
          <Hr />
          <Text
            style={{ fontSize: "12px", color: "#666666", textAlign: "center" }}
          >
            Questions?{" "}
            <Link href="mailto:[email protected]">Contact us</Link>
          </Text>
        </Container>
      </Body>
    </Html>
  );
}

Pass per-recipient data as React props. A transactional send targets one recipient, so the values are already known at render time and you keep full type safety.

Step 3 - Render to HTML

render() from @react-email/render v2 is asynchronous and returns the full HTML document.

import { render } from "@react-email/render";

import OrderConfirmation from "@/emails/order-confirmation";

const html = await render(
  <OrderConfirmation
    name="Jane"
    orderNumber="A-1042"
    total="$99.00"
    receiptUrl="https://yourdomain.com/receipts/A-1042"
  />,
);

Render in a .tsx file so JSX compiles. If your send logic lives in a .ts file, keep rendering in a separate .tsx module and import the resulting function.

Step 4 - Send Through Lumail

import { Lumail } from "lumail";

const lumail = new Lumail({ apiKey: process.env.LUMAIL_API_KEY! });

const { qstashMessageId } = await lumail.emails.send({
  to: "[email protected]",
  from: "[email protected]",
  subject: "Your order A-1042 is confirmed",
  content: html,
  contentType: "HTML",
  transactional: true,
});

The preview field is ignored for HTML content - only the Markdown and Tiptap pipelines inject it. The preheader comes from React Email's <Preview> component in your template.

Each recipient of a transactional send becomes a Lumail subscriber if they are not one already, and addresses on the blocklist are rejected before queueing.

The equivalent raw API call:

curl -X POST https://lumail.io/api/v1/emails \
  -H "Authorization: Bearer $LUMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "[email protected]",
    "from": "[email protected]",
    "subject": "Your order A-1042 is confirmed",
    "content": "<html>...rendered react email...</html>",
    "contentType": "HTML",
    "transactional": true
  }'

A success response means Lumail queued the email on the transactional priority lane. See Send Transactional Email for the full field list and response shape.

Complete Example

A single server function that renders and sends:

import { render } from "@react-email/render";
import { Lumail } from "lumail";

import OrderConfirmation from "@/emails/order-confirmation";

const lumail = new Lumail({ apiKey: process.env.LUMAIL_API_KEY! });

export async function sendOrderConfirmation(order: {
  email: string;
  name: string;
  orderNumber: string;
  total: string;
  receiptUrl: string;
}) {
  const html = await render(
    <OrderConfirmation
      name={order.name}
      orderNumber={order.orderNumber}
      total={order.total}
      receiptUrl={order.receiptUrl}
    />,
  );

  return lumail.emails.send({
    to: order.email,
    from: "[email protected]",
    subject: `Your order ${order.orderNumber} is confirmed`,
    content: html,
    contentType: "HTML",
    transactional: true,
  });
}

React Props vs Lumail Variables

Lumail replaces {{variable}} placeholders in HTML content before sending. Both mechanisms work, and they are good at different things.

DataUse
Order totals, names, URLs, anything your app already hasReact props
{{unsubscribeUrl}}, {{onlineUrl}}Lumail variables
Lumail custom subscriber fieldsLumail variables

Lumail owns the unsubscribe and view-in-browser URLs, so those must stay as placeholders.

Two limits are worth knowing before you rely on placeholders in a React Email template:

  • Placeholder names must be word characters. Hyphenated organization variables such as {{org-name}} are not substituted in HTML content and are delivered literally. Pass organization values as React props instead.
  • {{unsubscribeUrl}} resolves to an empty string on transactional sends, because a transactional email has no unsubscribe link. It is only meaningful when transactional is false.

See Send Email in HTML for the full variable list.

Escaping Braces in JSX

{{unsubscribeUrl}} inside JSX is parsed as a JavaScript expression containing an object literal, not as text. Always pass it as a string:

// Correct
<Link href={"{{unsubscribeUrl}}"}>Unsubscribe</Link>
<Text>Hello {"{{name}}"}</Text>

// Broken - JSX evaluates the braces
<Link href={{unsubscribeUrl}}>Unsubscribe</Link>

What Lumail Does to Your HTML

Your rendered document is sent through mostly untouched. Lumail applies these transformations:

  • Variable replacement - {{...}} placeholders are substituted with subscriber values.
  • Open tracking - a 1x1 pixel is inserted just before </body> when open tracking is on. React Email always emits a </body>, so the pixel lands inside the document.
  • Unsubscribe fallback - for non-transactional sends only, if the HTML contains no {{unsubscribeUrl}}, Lumail appends an unsubscribe block to the end of the string, after your closing </html>. Include {{unsubscribeUrl}} in your template to control where it renders.
  • Plain text version - generated by stripping HTML tags from your content.
  • Click tracking - link rewriting for HTML content targets Markdown-style [text](url) links, so <a href> URLs in a React Email template are delivered unchanged. Your original links stay intact, which is usually what you want for password resets and magic links.

Set tracking.open: false if you do not want the pixel at all.

Notes on Styling

  • Inline styles are the safest option and are what the React Email primitives use by default.
  • <Tailwind> works, since it inlines utility classes at render time. Responsive variants and pseudo-classes still fall back to a <style> block in <head>, which many clients ignore.
  • Keep the body width at or below 600px.
  • The auto-generated plain text version comes from a tag strip, so text inside <style> blocks can leak into it. Prefer inline styles and keep <head> CSS minimal.
  • Preview across clients before shipping a new template.

Preview Templates Locally

With react-email installed as a dev dependency, add a script:

{
  "scripts": {
    "email": "email dev --dir ./emails"
  }
}

Then run pnpm email and open the local preview to iterate on the template before wiring up the send.

Related

  • Send Transactional Email - full endpoint reference
  • Send Email in HTML - variables and email-safe HTML rules
  • SDK Resources - other SDK operations
  • Send Transactional Email with SMTP - for tools that cannot call the API
Send Transactional Email with SMTPBuild a V0 Capture Page

On This Page

When to Use ThisPrerequisitesStep 1 - Install the PackagesStep 2 - Write the TemplateStep 3 - Render to HTMLStep 4 - Send Through LumailComplete ExampleReact Props vs Lumail VariablesEscaping Braces in JSXWhat Lumail Does to Your HTMLNotes on StylingPreview Templates LocallyRelated

Lumail.io

Email marketing for AI agents. Send newsletters, manage subscribers, and build campaigns with ChatGPT, Claude, Hermes, or OpenClaw.

Plugins

Claude pluginCodex pluginChatGPTCLI + skillsIn-app integration

Product

BlogDocumentationChangelogDashboard

Company

AboutAccount

Legal

TermsPrivacy

8 The Green STE B, Dover Delaware 19901, United States

© 2025 Codelynx, LLC. All rights reserved.

Sign in