# Email Composer URL: /docs/web/components/email-composer Rich text email composer with sender-account picker, formatting toolbar, recipients, attachments, and i18n support. **Demo:** ```tsx 'use client'; import { useCallback, useMemo, useState } from 'react'; import { CalendarDays, FileText } from 'lucide-react'; import { EmailComposer, type EmailAttachment, type EmailComposerMentionUser, type EmailComposerSize, type EmailComposerSlashCommand, type EmailComposerSlashCommandContext, type EmailComposerVariant } from '@docyrus/ui/components/email-composer'; import { Button } from '@docyrus/ui/primitives/ui/button'; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@docyrus/ui/primitives/ui/dialog'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@docyrus/ui/primitives/ui/select'; import { Switch } from '@docyrus/ui/primitives/ui/switch'; import { Textarea } from '@docyrus/ui/primitives/ui/textarea'; import { cn } from '@docyrus/ui/primitives/lib/utils'; const MENTION_USERS: EmailComposerMentionUser[] = [ { id: '1', firstname: 'Alex', lastname: 'Johnson', email: 'alex@docyrus.com' }, { id: '2', firstname: 'Maria', lastname: 'Garcia', email: 'maria@docyrus.com' }, { id: '3', firstname: 'James', lastname: 'Smith', email: 'james@docyrus.com' }, { id: '4', firstname: 'Yuki', lastname: 'Tanaka', email: 'yuki@docyrus.com' }, { id: '5', firstname: 'Fatma', lastname: 'Yilmaz', email: 'fatma@docyrus.com' } ]; /** * Developer-drawn dialog for the `/template` slash command — the demo owns the * whole dialog (shell + Cancel / Send buttons) and only calls * `context.insert(html)` / `context.cancel()`. */ function TemplateSlashDialog({ context }: { context: EmailComposerSlashCommandContext }) { const [text, setText] = useState('Hi,\n\nThanks for reaching out — here is the summary we discussed.'); const handleInsert = useCallback(() => { const html = text .split('\n') .map(line => (line.trim() ? `

${line}

` : '
')) .join(''); context.insert(html); }, [context, text]); return ( ); } export function EmailComposerDemo() { const [variant, setVariant] = useState
Size
); } ``` ## Installation ```bash pnpm dlx @docyrus/cli add @docyrus/ui-email-composer ``` **Dependencies:** - [lucide-react](https://www.npmjs.com/package/lucide-react) - [class-variance-authority](https://www.npmjs.com/package/class-variance-authority) ## Usage ```tsx import { EmailComposer } from "@docyrus/ui/components/email-composer"; ); } ``` Notes: - The insertion position is saved when the command is picked, so the dialog can stay open as long as needed — `insert` lands the HTML exactly where the `/` was typed (falling back to the end of the body if that position no longer exists). - Render whatever dialog implementation you like (the `Dialog` primitive, a custom modal, a drawer) — the returned node is rendered inside the composer, and portal-based dialogs work as expected. - Inserted HTML is delivered to recipients as-is: use inline styles for any formatting, the app's stylesheet does not exist in email clients. - Omit `slashCommands` (or pass an empty array) to disable the feature entirely. ## Variants | Variant | Description | |---------|-------------| | `default` | Default style | | `outline` | Outline — `border-border` | | `minimal` | Minimal — `border-transparent shadow-none` | ## Sizes | Size | Description | |------|-------------| | `sm` | Small — `text-xs` | | `default` | Default — `text-sm` | | `lg` | Large — `text-base` | ## API Reference | Prop | Type | Default | |------|------|---------| | `variant` | `"default"` \| `"outline"` \| `"minimal"` | `"default"` | | `size` | `"sm"` \| `"default"` \| `"lg"` | `"default"` | | `accounts` | `EmailComposerAccount[]` | — | Sender accounts surfaced in the From dropdown. Hides the row when omitted. | | `selectedAccountId` | `string \| null` | — | Currently selected account id. | | `onSelectedAccountChange` | `(accountId: string) => void` | — | Called when the user picks a different account from the dropdown. | | `to` | `string[]` | — | | `onToChange` | `(to: string[]) => void` | — | | `cc` | `string[]` | — | | `onCcChange` | `(cc: string[]) => void` | — | | `bcc` | `string[]` | — | | `onBccChange` | `(bcc: string[]) => void` | — | | `subject` | `string` | — | | `onSubjectChange` | `(subject: string) => void` | — | | `body` | `string` | — | HTML body content (rich text via contentEditable) | | `onBodyChange` | `(body: string) => void` | — | Called with HTML string on body change | | `mentionUsers` | `EmailComposerMentionUser[]` | — | Users offered by the body editor's `@` mention autocomplete. Omit to disable mentions. | | `slashCommands` | `EmailComposerSlashCommand[]` | — | Developer-defined commands offered by the body editor's `/` autocomplete. Omit to disable slash commands. | | `onSend` | `() => void` | — | | `onAttach` | `() => void` | — | | `onDiscard` | `() => void` | — | | `sending` | `boolean` | — | | `disabled` | `boolean` | — | | `attachments` | `EmailAttachment[]` | — | | `onRemoveAttachment` | `(index: number) => void` | — | | `showToolbar` | `boolean` | `true` | | `signature` | `string` | — | HTML content of the email signature | | `onSignatureChange` | `(signature: string) => void` | — | Called when signature content changes. When provided, the signature area becomes editable | | `signatureVisible` | `boolean` | `true` | Whether the signature is visible (when signature is provided) | | `onSignatureVisibleChange` | `(visible: boolean) => void` | — | Called when the user toggles signature visibility | | `className` | `string` | — | ## Type Exports | Type | Description | |------|-------------| | `EmailComposerAccount` | `{ id; name; senderEmail; senderName?; kind?: 'tenant' \| 'user'; provider?: string \| null; isUserAccessible? }` — minimum shape consumed by the From dropdown. `provider` drives the brand icon (Google / Microsoft / AWS, else a generic mail icon). The Docyrus-backed account DTO (`DocyrusEmailAccount`) extends this. | | `EmailAttachment` | `{ name; size }` — local attachment metadata for display. | | `EmailComposerMentionUser` | `{ id; firstname?; lastname?; email?; photo? }` — user entry consumed by the `@` mention autocomplete. `email` powers the chip's `mailto:` link, `photo` the list avatar. | | `EmailComposerSlashCommand` | `{ id; label; description?; icon?; keywords?; getContent?; renderDialog? }` — a `/` command definition. `getContent` returns HTML to insert (async supported); `renderDialog` renders a fully developer-owned dialog instead. | | `EmailComposerSlashCommandContext` | `{ query; insert(html); cancel() }` — bridge API handed to `renderDialog`. `insert` injects at the trigger position and closes; `cancel` closes without inserting. | | `EmailComposerVariant` | `'default' \| 'outline' \| 'minimal'`. | | `EmailComposerSize` | `'sm' \| 'default' \| 'lg'`. |