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

# Sending HTML emails

> Build a reusable order-confirmation email with typed HTML rendering, inline styles, and a plain-text fallback using the BosonMail SDK.

Most email clients render HTML, but some users prefer or require plain text. This guide shows you how to build a reusable, typed order-confirmation function that renders both HTML and plain text from structured data, escapes user values, and sends the result through the BosonMail SDK.

## Why both HTML and text matter

The BosonMail SDK accepts `text` and `html` together in the same `sendEmail` call. Providing both improves compatibility across clients and accessibility tools. The SDK does not include a template engine: you build the markup in your own application code.

## Define the email data shape

Start with a TypeScript interface so the renderer is type-safe and reusable.

```typescript types.ts theme={null}
export interface OrderConfirmationData {
  orderId: string;
  customerName: string;
  items: Array<{ name: string; quantity: number; price: number }>;
  total: number;
  currency: string;
}
```

## Escape helper

User-supplied values must be escaped before they reach HTML or text output.

```typescript escape.ts theme={null}
function escapeHtml(input: string): string {
  return input
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}

function escapeText(input: string): string {
  // Minimal escaping for plain text: strip control characters
  return input.replace(/[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]/g, "");
}
```

## Render HTML with inline styles

Build a narrow HTML table layout. Keep styles inline because many clients ignore `<style>` blocks in `<head>`.

```typescript render-html.ts theme={null}
import { OrderConfirmationData } from "./types.ts";
import { escapeHtml } from "./escape.ts";

export function renderOrderConfirmationHtml(data: OrderConfirmationData): string {
  const rows = data.items
    .map(
      (item) => `
        <tr>
          <td style="padding:8px;border-bottom:1px solid #e5e7eb;">${escapeHtml(item.name)}</td>
          <td style="padding:8px;border-bottom:1px solid #e5e7eb;text-align:center;">${item.quantity}</td>
          <td style="padding:8px;border-bottom:1px solid #e5e7eb;text-align:right;">${escapeHtml(data.currency)} ${item.price.toFixed(2)}</td>
        </tr>
      `
    )
    .join("");

  return `
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Order Confirmation</title>
</head>
<body style="font-family:system-ui,-apple-system,sans-serif;color:#111827;background:#f9fafb;padding:24px;">
  <table width="100%" cellpadding="0" cellspacing="0" style="max-width:600px;margin:0 auto;background:#ffffff;border:1px solid #e5e7eb;border-radius:8px;">
    <tr>
      <td style="padding:24px;">
        <h2 style="margin:0 0 12px;font-size:20px;">Order confirmation</h2>
        <p style="margin:0 0 16px;color:#6b7280;">Hi ${escapeHtml(data.customerName)}, thank you for your order.</p>
        <p style="margin:0 0 16px;"><strong>Order ID:</strong> ${escapeHtml(data.orderId)}</p>
        <table width="100%" cellpadding="0" cellspacing="0" style="border-collapse:collapse;">
          <thead>
            <tr>
              <th align="left" style="padding:8px;border-bottom:2px solid #e5e7eb;">Item</th>
              <th align="center" style="padding:8px;border-bottom:2px solid #e5e7eb;">Qty</th>
              <th align="right" style="padding:8px;border-bottom:2px solid #e5e7eb;">Price</th>
            </tr>
          </thead>
          <tbody>${rows}</tbody>
        </table>
        <p style="margin:16px 0 0;text-align:right;font-weight:600;">
          Total: ${escapeHtml(data.currency)} ${data.total.toFixed(2)}
        </p>
      </td>
    </tr>
  </table>
</body>
</html>
  `.trim();
}
```

## Render plain text

Produce a matching plain-text version for clients that do not render HTML.

```typescript render-text.ts theme={null}
import { OrderConfirmationData } from "./types.ts";
import { escapeText } from "./escape.ts";

export function renderOrderConfirmationText(data: OrderConfirmationData): string {
  const lines = [
    `Order confirmation`,
    ``,
    `Hi ${escapeText(data.customerName)}, thank you for your order.`,
    `Order ID: ${escapeText(data.orderId)}`,
    ``,
    ...data.items.map(
      (item) =>
        `- ${escapeText(item.name)} | Qty: ${item.quantity} | ${escapeText(data.currency)} ${item.price.toFixed(2)}`
    ),
    ``,
    `Total: ${escapeText(data.currency)} ${data.total.toFixed(2)}`,
  ];
  return lines.join("\n");
}
```

## Send the email

Wire the renderer into a reusable sender function.

```typescript send-order-confirmation.ts theme={null}
import BosonMailClient from "@bosonware-ent/bosonmail";
import { OrderConfirmationData } from "./types.ts";
import { renderOrderConfirmationHtml } from "./render-html.ts";
import { renderOrderConfirmationText } from "./render-text.ts";

const apiKey = process.env.BOSONMAIL_API_KEY;
if (!apiKey) throw new Error("BOSONMAIL_API_KEY is not set");

const client = new BosonMailClient({ apiKey });

export async function sendOrderConfirmation(
  to: string,
  data: OrderConfirmationData
) {
  const result = await client.sendEmail(to, "Your order confirmation", {
    text: renderOrderConfirmationText(data),
    html: renderOrderConfirmationHtml(data),
  });

  if (!result.success) {
    console.error("Failed to send order confirmation:", result.errorMessage);
    return result;
  }

  console.log("Order confirmation sent. Message ID:", result.messageId);
  return result;
}
```

## Usage example

```typescript example.ts theme={null}
import { sendOrderConfirmation } from "./send-order-confirmation.ts";

await sendOrderConfirmation("customer@example.com", {
  orderId: "ORD-2026-8842",
  customerName: "Amina Ochieng",
  items: [
    { name: "BosonMail Pro (1 year)", quantity: 1, price: 1200.0 },
    { name: "Extra outbound pack", quantity: 2, price: 150.0 },
  ],
  total: 1500.0,
  currency: "KSh",
});
```

## Testing tips across clients

> [accordion] Keep HTML size reasonable
>
> Large HTML bodies increase rejection risk and slow rendering. Aim for under 100 KB of markup. Move heavy images to attachments or host them externally.

> [accordion] Test in multiple clients
>
> Use services such as Litmus or Email on Acid to preview how your table-based layout renders in Outlook, Gmail, Apple Mail, and mobile clients. Inline styles are the safest cross-client approach.

> [accordion] Avoid external CSS and web fonts
>
> Many email clients strip `<style>` blocks and ignore `@font-face`. Use system font stacks and inline `style` attributes on every element that needs styling.

> [accordion] Validate before sending
>
> Run your HTML through a validator or linter to catch unclosed tags and broken tables. A malformed email may still be accepted by the API but render poorly for the recipient.

> [!TIP]
>
> The SDK does not provide template features such as partials, loops, or layout inheritance. If you need those, bring your own template engine (for example, Handlebars, EJS, or React email renderers) and pass the final strings to `sendEmail`.

## Next steps

> [card] [Sending with attachments](https://docs.bosonware.com/guides/attachments.md)
>
> Attach PDF invoices and CSV exports to your order confirmations.

> [card] [Multiple recipients](https://docs.bosonware.com/guides/multiple-recipients.md)
>
> Send the same confirmation to billing and shipping contacts using CC and BCC.

> [card] [Monitoring delivery](https://docs.bosonware.com/guides/monitoring-delivery.md)
>
> Track whether the confirmation was delivered, bounced, or deferred.

> [card] [SDK reference: HTML emails](https://docs.bosonware.com/sdk/html-emails.md)
>
> Read the concise SDK reference for the `html` and `text` body options.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.