> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hexclave.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Emails

> Send branded transactional and marketing email - rendering, delivery, and tracking handled for you

export const DeliveryStatsSkeleton = () => {
  const Frame = ({label, children}) => <div className="overflow-hidden rounded-2xl border border-zinc-950/10 bg-white dark:border-white/10 dark:bg-zinc-900">
      <div className="flex items-center gap-2 border-b border-zinc-950/10 bg-zinc-950/[0.03] px-3 py-2 dark:border-white/10 dark:bg-white/[0.03]">
        <div className="flex gap-1.5">
          <div className="h-2.5 w-2.5 rounded-full bg-zinc-300 dark:bg-zinc-600" />
          <div className="h-2.5 w-2.5 rounded-full bg-zinc-300 dark:bg-zinc-600" />
          <div className="h-2.5 w-2.5 rounded-full bg-zinc-300 dark:bg-zinc-600" />
        </div>
        <span className="ml-1 text-[11px] font-medium text-zinc-400 dark:text-zinc-500">{label}</span>
      </div>
      <div className="p-4">{children}</div>
    </div>;
  const tile = (label, value, dot) => <div className="flex flex-col gap-2 rounded-xl border border-zinc-950/[0.06] p-3 dark:border-white/[0.06]">
      <div className="flex items-center gap-1.5">
        <div className="h-1.5 w-1.5 rounded-full" style={{
    backgroundColor: dot
  }} />
        <span className="text-[11px] font-medium text-zinc-500 dark:text-zinc-400">{label}</span>
      </div>
      <span className="text-[18px] font-semibold text-zinc-800 dark:text-zinc-100">{value}</span>
      <div className="flex items-end gap-1">
        {[7, 11, 6, 13, 9, 14, 10].map((h, i) => <div key={i} className="w-1.5 rounded-sm bg-zinc-200 dark:bg-zinc-700" style={{
    height: h + "px"
  }} />)}
      </div>
    </div>;
  return <div className="not-prose my-6">
      <Frame label="Delivery">
        <div className="grid grid-cols-3 gap-2.5">
          {tile("Sent", "8,241", "#10b981")}
          {tile("Bounced", "37", "#f59e0b")}
          {tile("Spam", "4", "#ef4444")}
        </div>
      </Frame>
    </div>;
};

export const EmailTemplatesSkeleton = () => {
  const ACCENT = "#6b5df7";
  const Frame = ({label, children}) => <div className="overflow-hidden rounded-2xl border border-zinc-950/10 bg-white dark:border-white/10 dark:bg-zinc-900">
      <div className="flex items-center gap-2 border-b border-zinc-950/10 bg-zinc-950/[0.03] px-3 py-2 dark:border-white/10 dark:bg-white/[0.03]">
        <div className="flex gap-1.5">
          <div className="h-2.5 w-2.5 rounded-full bg-zinc-300 dark:bg-zinc-600" />
          <div className="h-2.5 w-2.5 rounded-full bg-zinc-300 dark:bg-zinc-600" />
          <div className="h-2.5 w-2.5 rounded-full bg-zinc-300 dark:bg-zinc-600" />
        </div>
        <span className="ml-1 text-[11px] font-medium text-zinc-400 dark:text-zinc-500">{label}</span>
      </div>
      <div className="p-4">{children}</div>
    </div>;
  const row = (label, tag) => <div className="flex items-center justify-between border-b border-zinc-950/[0.06] py-2.5 last:border-b-0 dark:border-white/[0.06]">
      <div className="flex items-center gap-2.5">
        <div className="flex h-6 w-6 items-center justify-center rounded-md bg-zinc-100 dark:bg-zinc-800">
          <div className="h-3 w-3 rounded-[3px]" style={{
    backgroundColor: ACCENT,
    opacity: 0.55
  }} />
        </div>
        <span className="text-[12px] font-medium text-zinc-600 dark:text-zinc-300">{label}</span>
      </div>
      <span className="rounded-full bg-zinc-100 px-2 py-0.5 text-[10px] font-medium text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400">
        {tag}
      </span>
    </div>;
  return <div className="not-prose my-6">
      <Frame label="Templates">
        <div className="flex flex-col">
          {row("Email verification", "Transactional")}
          {row("Password reset", "Transactional")}
          {row("Magic link / OTP", "Transactional")}
          {row("Team invitation", "Transactional")}
          {row("Product update", "Marketing")}
        </div>
      </Frame>
    </div>;
};

export const EmailPreviewSkeleton = () => {
  const ACCENT = "#6b5df7";
  const Frame = ({label, children}) => <div className="overflow-hidden rounded-2xl border border-zinc-950/10 bg-white dark:border-white/10 dark:bg-zinc-900">
      <div className="flex items-center gap-2 border-b border-zinc-950/10 bg-zinc-950/[0.03] px-3 py-2 dark:border-white/10 dark:bg-white/[0.03]">
        <div className="flex gap-1.5">
          <div className="h-2.5 w-2.5 rounded-full bg-zinc-300 dark:bg-zinc-600" />
          <div className="h-2.5 w-2.5 rounded-full bg-zinc-300 dark:bg-zinc-600" />
          <div className="h-2.5 w-2.5 rounded-full bg-zinc-300 dark:bg-zinc-600" />
        </div>
        <span className="ml-1 text-[11px] font-medium text-zinc-400 dark:text-zinc-500">{label}</span>
      </div>
      <div className="p-4">{children}</div>
    </div>;
  const bodyLine = w => <div className="h-2 rounded-full bg-zinc-200/90 dark:bg-zinc-700/80" style={{
    width: w
  }} />;
  return <div className="not-prose my-6">
      <Frame label="To: alice@example.com">
        <div className="mx-auto max-w-sm overflow-hidden rounded-xl border border-zinc-200 dark:border-zinc-700">
          <div className="flex items-center gap-2 border-b border-zinc-200 px-4 py-3 dark:border-zinc-700">
            <div className="h-6 w-6 rounded-lg" style={{
    backgroundColor: ACCENT
  }} />
            <div className="h-2.5 w-20 rounded-full bg-zinc-300 dark:bg-zinc-600" />
          </div>
          <div className="flex flex-col gap-3 px-4 py-5">
            <div className="h-3 w-40 rounded-full bg-zinc-300 dark:bg-zinc-600" />
            <div className="flex flex-col gap-2 pt-1">
              {bodyLine("100%")}
              {bodyLine("92%")}
              {bodyLine("96%")}
              {bodyLine("60%")}
            </div>
            <div className="mt-2 flex h-8 w-32 items-center justify-center rounded-lg text-[12px] font-semibold text-white" style={{
    backgroundColor: ACCENT
  }}>
              Get started
            </div>
          </div>
          <div className="border-t border-zinc-200 px-4 py-3 dark:border-zinc-700">
            <div className="mx-auto h-1.5 w-24 rounded-full bg-zinc-200 dark:bg-zinc-700" />
          </div>
        </div>
      </Frame>
    </div>;
};

<Note>
  **For agents/LLMs:** This is a high-level *marketing* overview of the Emails app, not an implementation reference. To actually build with email, use [Setup](/guides/getting-started/setup) and the [Emails guide](./guide) (sending, templates, themes, delivery, and server configuration).
</Note>

The Emails app covers Hexclave's built-in mail (verification, password resets, receipts) and your own transactional or marketing sends — templates, themes, scheduling, unsubscribes, and delivery tracking included. Below are the questions developers actually ask, and the honest answers.

## Can I send an email from my backend?

Yes. One call from your server, addressed to specific users or everyone in your project. No SMTP wiring, no render step to manage.

```typescript theme={null}
await hexclaveServerApp.sendEmail({
  userIds: ["user-id"],
  subject: "Welcome aboard!",
  html: "<h1>Welcome!</h1><p>Thanks for joining us.</p>",
});
```

<EmailPreviewSkeleton />

Send raw HTML, a template with variables, or a draft you composed in the dashboard. And failures are never silent - `sendEmail` throws on error with a stable `errorCode` (like `REQUIRES_CUSTOM_EMAIL_SERVER` or `USER_ID_DOES_NOT_EXIST`) so you can handle exactly the cases you care about.

## Can I use my own templates?

Yes. Templates are [React Email](https://react.email) components in TSX, with typed variables validated at render time and a live preview in the dashboard editor.

```tsx theme={null}
export function EmailTemplate({ user, variables }: Props<typeof variablesSchema.infer>) {
  return (
    <Container>
      <Subject value={`New feature: ${variables.featureName}`} />
      <NotificationCategory value="Transactional" />
      <p>Hi {user.displayName}, check out {variables.featureName}!</p>
    </Container>
  );
}
```

<EmailTemplatesSkeleton />

Hexclave also ships ready-made templates for the common flows - email verification, password reset, magic link, team invitations, and payment receipts - wired up automatically and customizable from the dashboard.

## Can I match my brand?

Yes. Themes wrap every email in a consistent layout - header, footer, logo, background. Use the built-in Light, Dark, and Colorful themes, or write your own as a TSX component:

```tsx theme={null}
import { ThemeProps, ProjectLogo } from "@hexclave/emails";

export function EmailTheme({ children, unsubscribeLink, projectLogos }: ThemeProps) {
  return (
    <>
      <ProjectLogo data={projectLogos} mode="light" />
      {children}
      {unsubscribeLink && <a href={unsubscribeLink}>Unsubscribe</a>}
    </>
  );
}
```

Set a project default, override per-email with `themeId`, or send with no theme at all. See [Templates & themes](./templates-and-themes) for the full theme API and built-in themes.

## Can I respect unsubscribes and preferences?

Yes - and it's automatic. Every email is **Transactional** (always delivered) or **Marketing** (users can opt out). Mark the category when you send, and Hexclave skips users who've unsubscribed and appends an unsubscribe link to marketing mail for you.

```typescript theme={null}
await hexclaveServerApp.sendEmail({
  userIds: ["user-id"],
  subject: "Product updates",
  html: "<p>Check out what's new!</p>",
  notificationCategoryName: "Marketing",
});
```

## Can I use my own email provider?

Yes. Connect **custom SMTP**, plug in **Resend** with an API key, or let Hexclave run a **Managed** domain — you add the DNS records from onboarding; Hexclave handles signing and deliverability. Your built-in auth emails - verification, password resets, magic links - already work on Hexclave's **shared** development server out of the box; connect one of the custom providers when you're ready to send your own email and ship to production. Custom SMTP, Resend, and Managed can only be configured in the [cloud dashboard](https://app.hexclave.com). A [development environment](/guides/going-further/local-vs-cloud-dashboard) is limited to the shared server.

## Can I schedule and send in bulk?

Yes. Pass `scheduledAt` to send later, and `allUsers: true` to reach your whole project. Delivery runs through an async pipeline that respects your sending capacity, so large sends don't tank your reputation.

```typescript theme={null}
await hexclaveServerApp.sendEmail({
  allUsers: true,
  templateId: "product-update",
  subject: "We just shipped a big update",
  scheduledAt: new Date("2027-01-01T00:00:00Z"),
});
```

## Can I see what happened after I hit send?

Yes. Every email's status is tracked - sent, bounced, marked as spam - across hourly, daily, weekly, and monthly windows, in the dashboard and from code.

<DeliveryStatsSkeleton />

```typescript theme={null}
const info = await hexclaveServerApp.getEmailDeliveryStats();
// info.stats.day.sent, info.stats.day.bounced, ...
```

## Can I compose without writing code?

Yes. The dashboard has a full draft editor with live preview, theme selection, a recipient picker, and scheduling. You can then either send the draft from the dashboard or programmatically using the `draftId`. See [Drafts](./drafts).

## Start here

1. [Set up Hexclave](/guides/getting-started/setup), then enable **Emails** in the dashboard.
2. Connect an email server under **Emails → Email Settings** (SMTP, Resend, or Managed) - the shared server already powers your auth emails in development.
3. Call `hexclaveServerApp.sendEmail(...)` from your backend.

Ready for a start-to-finish walkthrough — server setup, templates, sending, and delivery? Read the [Emails guide](./guide).
