Skip to content
 
 

Latest commit

 

History

693 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

maily.cn avatar: a suited character wearing colorful glasses

maily.cn

A production-ready Maily email editor, installed as source through shadcn.

Bring your content, use the pre-designed blocks, and own every line that lands in your app.

MIT license Upstream maily.to repository Sponsor brokeboiflex on GitHub

Colorful maily.cn illustration featuring the maintainer character

maily.cn is a fork of maily.to, the TipTap-based WYSIWYG editor for composing beautiful, mobile-ready emails from pre-designed blocks.

The upstream project publishes Maily as npm packages. This fork takes a different distribution path: install only the editor, optional mailbox, and renderer source you actually use through the shadcn registry. You own those files, theme them, and change them like any other shadcn component.

Try the editor and mailbox components in the live playground, including light, dark, and system mode selection powered by shadcn-theme-provider.

Maily itself was created by Arik Chakma and its contributors. maily.cn maintains a source-owned shadcn distribution and the production hardening documented below.

Start using

Add the registry namespace to your existing shadcn project's components.json:

{
  "registries": {
    "@maily": "https://raw.githubusercontent.com/brokeboiflex/maily.cn/main/playground/public/r/{name}.json",
  },
}

Then install the editor:

npx shadcn@latest add @maily/maily-editor

Add the optional parts only when the application uses them:

npx shadcn@latest add @maily/maily-mailbox
npx shadcn@latest add @maily/maily-render

maily-mailbox depends on maily-editor, so adding it to a fresh project installs both. @maily/maily remains available as the backward-compatible full install. Every item declares only the npm packages and stock shadcn primitives imported by its own emitted source. The editor item also wires the Tailwind typography plugin used by the writing surface.

Why use maily.cn?

Designing email that behaves consistently across clients is hard. Maily gives you an opinionated editor with reusable blocks, while maily.cn makes that editor fit the way modern shadcn applications are built.

  • Source-owned installation — no opaque editor UI dependency after installation.
  • Real shadcn primitives — works with both current Radix and Base UI component styles.
  • Host-controlled theming — plain Tailwind v4 utilities and the consumer's shadcn tokens.
  • Generic i18n — replace every user-facing label without coupling to an i18n framework.
  • Caller-driven image upload — provide your own upload handler and storage backend.
  • Email-safe rendering — turn the editor JSON into HTML independently of editor theming.
  • Dependency-light custom HTML — edit and preview HTML without shipping a client-side syntax-language bundle.
  • Consumer icon choice — icon placeholders resolve to the library selected in components.json.

The maintained shadcn alignment boundary lists every host-owned primitive and explains the remaining editor-specific composites.

Included blocks

  • Logo and cover layouts
  • Buttons and variants
  • Variables
  • Text formatting and headings
  • Images and inline images
  • Alignment and spacing controls
  • Dividers and spacers
  • Footers
  • Inline code and HTML
  • Link cards
  • Sections and columns
  • Repeat blocks
  • Conditional content

What gets installed

Registry item Target Import Purpose
maily-editor components/maily/** @/components/maily The <Editor /> WYSIWYG email composer.
maily-mailbox components/maily/mailbox/** @/components/maily/mailbox Optional inbox / sent / drafts view.
maily-render lib/maily-render/** @/lib/maily-render Editor JSON to email-safe HTML.
maily All targets above All imports above Backward-compatible full install.

The root @/components/maily barrel intentionally exports the editor only. Import mailbox code from @/components/maily/mailbox so editor-only consumers do not compile the optional mailbox surface.

Requirements

  • React 18 or 19
  • Tailwind CSS v4 with standard shadcn theme tokens
  • A project initialized with the shadcn CLI (components.json present)

The registry automatically declares its stock shadcn dependencies and adds @plugin "@tailwindcss/typography"; for the editor's prose content area.

Editor usage

import { Editor } from '@/components/maily';

export function ComposeEmail() {
  return (
    <Editor
      contentJson={{ type: 'doc', content: [] }}
      onUpdate={(editor) => {
        console.log(editor.getJSON());
      }}
    />
  );
}

<Editor /> includes the toolbar, slash-command menu, bubble menus, and writing surface. Its chrome and canvas inherit the host application's light or dark theme. Interactive chrome also inherits the host's installed shadcn primitives: buttons, toggles, toggle groups, menus, popovers, tabs, command lists, inputs, tooltips, separators, and keyboard hints come from the consumer's selected shadcn style.

Key props

All props are optional. The editor accepts initial JSON or HTML and reports changes through callbacks.

Prop Type Description
contentJson JSONContent Initial TipTap JSON: a document node or an array of nodes.
contentHtml string Initial HTML, used when contentJson is absent.
onCreate (editor) => void Called when the editor instance is ready.
onUpdate (editor) => void Called on changes; read editor.getJSON() here.
editable boolean Read-only toggle. Defaults to true.
extensions AnyExtension[] Additional TipTap extensions merged with the defaults.
blocks BlockGroupItem[] Replacement slash-command block list.
config object Chrome toggles and class hooks.

config supports hasMenuBar, hideContextMenu, spellCheck, autofocus, immediatelyRender, initialViewMode, renderPreview, wrapClassName, toolbarClassName, bodyClassName, renderPreviewClassName, and contentClassName.

The toolbar includes a Design / Render toggle. Design mode keeps the editable TipTap canvas active. Render mode shows a read-only iframe preview from editor.getHTML() by default; pass config.renderPreview when your app wants to mount its own preview component backed by @/lib/maily-render or a server-side rendering endpoint.

Translation

Every user-facing string is read through the framework-agnostic labels contract. Omit it for the English defaults or provide a complete MailyLabels object.

import { Editor, defaultLabels, type MailyLabels } from '@/components/maily';

const labels: MailyLabels = {
  ...defaultLabels,
  'toolbar.bold': 'Pogrubienie',
  'toolbar.italic': 'Kursywa',
};

<Editor labels={labels} />;

MailyLabels is intentionally exhaustive. When the editor adds new UI copy, a complete language file fails TypeScript until that key is translated.

Image upload

Storage stays under the consumer's control. Configure the image upload extension with a handler that accepts a file and returns its public URL:

import { ImageUploadExtension } from '@/components/maily/editor/extensions';

<Editor
  extensions={[
    ImageUploadExtension.configure({
      onImageUpload: async (file) => uploadImage(file),
    }),
  ]}
/>;

Users can still paste a URL when an upload handler is not appropriate.

Mailbox view

<MailboxView /> is an optional application-shell component for the CRM/Veyme style inbox / sent / drafts / bounced surface. It owns local folder, search, selection, compose, draft, and polling state inside a shadcn ResizablePanelGroup shell, but it does not assume a backend. Wire your own API through the dataSource adapter.

import {
  MailboxView,
  defaultMailboxLabels,
  type MailyMailboxDataSource,
} from '@/components/maily/mailbox';

const dataSource: MailyMailboxDataSource = {
  listMessages: ({ folder, q }) => api.mail.messages({ folder, q }),
  getMessage: (messageId) => api.mail.message(messageId),
  getCounts: () => api.mail.counts(),
  listContactSuggestions: ({ q, limit }) => api.contacts.search({ q, limit }),
  createDraft: (draft) => api.mail.createDraft(draft),
  updateDraft: (messageId, draft) => api.mail.updateDraft(messageId, draft),
  discardDraft: (messageId) => api.mail.discardDraft(messageId),
  sendDraft: (messageId) => api.mail.sendDraft(messageId),
  runMessageAction: ({ messageId, action, value }) =>
    api.mail.runMessageAction(messageId, action, value),
};

<MailboxView
  account={{ address: 'hello@example.com' }}
  dataSource={dataSource}
  labels={defaultMailboxLabels}
/>;

Recipient fields support autocomplete when the host provides contacts through dataSource.listContactSuggestions or the contactSuggestions prop. Each suggestion uses { address, displayName } and is matched by both email address and display name.

Reader actions are Gmail-like but backend-agnostic. Reply and forward seed the existing draft flow; favorite, archive, delete, mark unread, report, print, download, and show-original controls call dataSource.runMessageAction when the host supplies it.

defaultMailboxLabels is exhaustive, matching the editor translation contract: copy it, translate every value, and pass the complete object back as labels.

Font selection

Select text in the editor to choose from the complete Fontsource catalog. The picker fetches catalog metadata only when opened, virtualizes its results, and loads preview WOFF2 files only for visible rows, so the font library is not bundled into the installed component.

The chosen Fontsource package version, subset, weights, fallback, and family are stored on TipTap's textStyle mark. Saved editor JSON is therefore enough for the server-side renderer to emit version-pinned @font-face declarations without querying Fontsource while an email is being sent. Unsupported email clients use the family-specific email-safe fallback.

The Fontsource API and jsDelivr font CDN must be permitted by the host application's content security policy for catalog previews. Sent emails still render readable fallback fonts when a client blocks remote web fonts.

Renderer usage

Install @maily/maily-render before using the server-side renderer.

import { render } from '@/lib/maily-render';

const html = await render(editorJson, {
  preview: 'Inbox preview text',
  theme: {
    /* optional rendered-email theme overrides */
  },
});

The renderer is independent of the editor's on-screen theme and produces the final email-client-safe HTML from the saved JSON.

Upgrading an existing full install

The shadcn CLI overwrites current registry files but does not delete files removed from a newer item. After upgrading, remove these obsolete private primitive or icon copies if they still exist and are not locally modified:

components/maily/editor/components/popover.tsx
components/maily/editor/components/ui/divider.tsx
components/maily/editor/components/ui/tooltip.tsx
components/maily/editor/components/icons/border-color.tsx
components/maily/editor/components/icons/grid-lines.tsx
components/maily/editor/components/icons/text-direction-icon.tsx

When intentionally moving from the legacy full item to maily-editor, also remove components/maily/mailbox/** and lib/maily-render/** after confirming the application no longer imports them.

Theming and customization

  • Change the host's shadcn theme tokens to restyle the editor globally.
  • Pass layout utilities through the editor's config class hooks.
  • Edit the installed source for deeper changes; that is the point of this distribution.
  • The actual sent email is styled by the renderer, not by the editor chrome.

Sponsoring

If maily.cn saves you time, helps you ship, or makes you money, sponsorships are very welcome. I will happily accept them xD.

Sponsor maily.cn through GitHub Sponsors

You can also support the original project through Arik Chakma's GitHub Sponsors.

Contributing and local development

This is a pnpm and Turborepo monorepo. packages/core, packages/render, and packages/shared are the source of truth. registry/**, registry.json, and the served playground registry are generated.

pnpm install
pnpm dev
pnpm test
pnpm registry:build
pnpm playground:sync

Read AGENTS.md for the architecture and repository conventions. The real consumer harness is documented in playground/README.md.

Credits

maily.cn is built on maily.to by Arik Chakma and contributors. The fork is maintained by brokeboiflex.

License

MIT. See license.

About

Production-ready Maily email editor installed as source through shadcn — live playground: https://brokeboiflex.github.io/maily.cn/

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages