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

# Mailer

> Send transactional emails with React-rendered templates behind a single IMailer interface.

The `@ooneex/mailer` component sends transactional emails. The built-in mailer renders a React node to HTML server-side so your email bodies are plain components. Every sender implements the same `IMailer` interface — a single `send` method — so callers stay identical no matter how mail is delivered. A `MailerLayout` component gives you a styled header/body/footer shell to build templates on.

## Why this component

* **One interface, container-managed.** Every mailer implements `IMailer` (`send`) and is registered with a decorator, so you resolve and inject it like any other service.
* **React templates.** Email bodies are React nodes rendered to HTML with `renderToString` — compose them like UI, not string concatenation.
* **Layout system.** `MailerLayout` ships with `Header`, `Body`, and `Footer` subcomponents and sensible email-safe defaults.
* **Environment configuration.** Credentials and the default sender are read from environment variables; per-send overrides are supported.

## How it works

You resolve a mailer from the container and call `send`. The mailer renders the `content` React node to HTML, resolves the sender (from the call or the environment), and delivers the message.

| Member         | Purpose                                                  |
| -------------- | -------------------------------------------------------- |
| `send(config)` | Render `content` and send the email. Resolves to `void`. |

The `config` object passed to `send`:

| Field     | Type                                | Purpose                                                           |
| --------- | ----------------------------------- | ----------------------------------------------------------------- |
| `to`      | `string[]`                          | Recipient email addresses.                                        |
| `subject` | `string`                            | Email subject line.                                               |
| `content` | `React.ReactNode`                   | Body rendered to HTML server-side.                                |
| `from`    | `{ name: string; address: string }` | Optional sender override; falls back to the environment defaults. |

The sender is resolved per call: `config.from` wins, otherwise `MAILER_SENDER_NAME` and `MAILER_SENDER_ADDRESS` are used. If neither yields a name and address, `send` throws.

## Environment variables

| Variable                | Required | Purpose                                                                          |
| ----------------------- | -------- | -------------------------------------------------------------------------------- |
| `MAILER_SENDER_NAME`    | No       | Default sender name. Required at send time unless `from.name` is provided.       |
| `MAILER_SENDER_ADDRESS` | No       | Default sender address. Required at send time unless `from.address` is provided. |

```bash theme={null}
MAILER_SENDER_NAME=Acme
MAILER_SENDER_ADDRESS=hello@acme.com
```

## Decorator and usage

### `@decorator.mailer()`

Registers a mailer class with the container. It accepts an optional scope (defaults to singleton). The built-in mailer is already decorated under the `mailer` token; use the decorator on your own mailers so they can be resolved and injected.

Resolve the mailer from the container and send an email — the body is just a React node:

```typescript theme={null}
import { container } from "@ooneex/container";
import { MailerLayout } from "@ooneex/mailer";
import type { IMailer } from "@ooneex/mailer";

const mailer = container.get<IMailer>("mailer");

await mailer.send({
  to: ["user@example.com"],
  subject: "Welcome to Acme!",
  content: (
    <MailerLayout>
      <MailerLayout.Header />
      <MailerLayout.Body>
        <h1>Welcome aboard</h1>
        <p>Thanks for signing up.</p>
      </MailerLayout.Body>
      <MailerLayout.Footer />
    </MailerLayout>
  ),
});
```

Override the sender per call when a message should come from a specific address:

```typescript theme={null}
await mailer.send({
  to: ["user@example.com"],
  subject: "Important update",
  content: <UpdateEmail />,
  from: { name: "Support Team", address: "support@acme.com" },
});
```

A typical mailer wraps a template and delegates to the injected `IMailer`. Register it with the decorator and inject the underlying mailer under the `mailer` token:

```typescript theme={null}
import { inject } from "@ooneex/container";
import { decorator } from "@ooneex/mailer";
import type { IMailer } from "@ooneex/mailer";
import { type WelcomeMailerPropsType, WelcomeMailerTemplate } from "./WelcomeMailerTemplate";

@decorator.mailer()
export class WelcomeMailer implements IMailer {
  constructor(
    @inject("mailer")
    private readonly mailer: IMailer,
  ) {}

  public async send(config: {
    to: string[];
    subject: string;
    from?: { name: string; address: string };
    data?: WelcomeMailerPropsType;
  }): Promise<void> {
    const { data, ...rest } = config;

    await this.mailer.send({
      ...rest,
      content: WelcomeMailerTemplate(data),
    });
  }
}
```

## Exceptions

The component throws `MailerException` when it is misconfigured or a send cannot complete. It carries a machine-readable `key`, a human-readable `message`, and a `data` object.

| Key                      | When                                                                                                   |
| ------------------------ | ------------------------------------------------------------------------------------------------------ |
| `EMAIL_SEND_FAILED`      | `send` is called with no resolvable sender name (no `from.name` and no `MAILER_SENDER_NAME`).          |
| `EMAIL_OPERATION_FAILED` | `send` is called with no resolvable sender address (no `from.address` and no `MAILER_SENDER_ADDRESS`). |

```typescript theme={null}
import { MailerException } from "@ooneex/mailer";
import { container } from "@ooneex/container";
import type { IMailer } from "@ooneex/mailer";

const mailer = container.get<IMailer>("mailer");

try {
  await mailer.send({ to: ["user@example.com"], subject: "Hi", content: <Email /> });
} catch (error) {
  if (error instanceof MailerException) {
    logger.error(`Mailer error [${error.key}]: ${error.message}`, error.data);
  } else {
    throw error;
  }
}
```

## Best practices

* **Set the default sender in the environment.** Configure `MAILER_SENDER_NAME` and `MAILER_SENDER_ADDRESS` once so every send has a fallback, and override `from` only when a message needs a different address.
* **Build bodies on `MailerLayout`.** Compose with `Header`, `Body`, and `Footer` for email-safe, consistent markup instead of raw HTML strings.
* **Keep templates as props-driven functions.** Pass data in, return a React node — easy to render in tests without sending anything.
* **Wrap a template per email type.** A dedicated mailer (e.g. `WelcomeMailer`) that delegates to the injected `IMailer` keeps subjects, templates, and props in one place.
* **Catch `MailerException` at send sites.** Inspect `error.key` to distinguish configuration problems from delivery problems, and never leak credentials.
* **Resolve through the container.** Inject mailers rather than constructing them so the credentials and defaults are wired consistently.

## CLI command

Scaffold a mailer, its JSX template, and their tests with the generator. It writes the class under `modules/<module>/src/mailers/<Name>Mailer.ts`, the template alongside it, and installs `@ooneex/mailer` if it is missing.

```bash theme={null}
# Interactive: prompts for the name
ooneex mailer:create

# Provide the name
ooneex mailer:create --name=Welcome

# Target a module and overwrite
ooneex mailer:create --name=Welcome --module=auth --override
```

| Option       | Description                                                       | Default             |
| ------------ | ----------------------------------------------------------------- | ------------------- |
| `--name`     | Mailer class name. The `Mailer` suffix is appended automatically. | Prompted if omitted |
| `--module`   | Target module the files are generated into.                       | `shared`            |
| `--override` | Overwrite an existing mailer without prompting.                   | `false`             |

The generated mailer wraps its template and delegates to the injected `IMailer`, ready for you to refine the subject and props:

```typescript theme={null}
import { inject } from "@ooneex/container";
import { decorator } from "@ooneex/mailer";
import type { IMailer } from "@ooneex/mailer";
import { type WelcomeMailerPropsType, WelcomeMailerTemplate } from "./WelcomeMailerTemplate";

@decorator.mailer()
export class WelcomeMailer implements IMailer {
  constructor(
    @inject("mailer")
    private readonly mailer: IMailer,
  ) {}

  public async send(config: {
    to: string[];
    subject: string;
    from?: { name: string; address: string };
    data?: WelcomeMailerPropsType;
  }): Promise<void> {
    const { data, ...rest } = config;

    await this.mailer.send({
      ...rest,
      content: WelcomeMailerTemplate(data),
    });
  }
}
```

The companion template is a props-driven function built on `MailerLayout`:

```tsx theme={null}
import { MailerLayout } from "@ooneex/mailer";

export type WelcomeMailerPropsType = {
  link: string;
};

export const WelcomeMailerTemplate = (props?: WelcomeMailerPropsType) => (
  <MailerLayout>
    <MailerLayout.Header />
    <MailerLayout.Body>
      <a href={props?.link}>Login</a>
    </MailerLayout.Body>
    <MailerLayout.Footer />
  </MailerLayout>
);
```

See [mailer:create](/cli/commands/mailer-create) for the full command reference.

## Use with Claude and Codex

The generator ships a matching `mailer:create` skill. It runs the scaffold and then guides your AI agent through completing the mailer — refining the `send` config, filling in the template props, and wiring up its tests. Initialize the skills once for your agent:

<Tabs>
  <Tab title="Claude">
    ```bash theme={null}
    ooneex claude:init
    ```

    Then ask Claude in natural language — it maps the request to the generator, runs it, and fills in the implementation:

    ```text Prompt icon="terminal" wrap theme={null}
    Create a mailer that sends a welcome email after sign-up.
    ```
  </Tab>

  <Tab title="Codex">
    ```bash theme={null}
    ooneex codex:init
    ```

    Then ask Codex in natural language — it maps the request to the generator, runs it, and fills in the implementation:

    ```text Prompt icon="terminal" wrap theme={null}
    Create a mailer that sends a welcome email after sign-up.
    ```
  </Tab>
</Tabs>

For example, the prompt above maps to `mailer:create --name=Welcome`, then builds the welcome template and completes the `send` method.
