Skip to main content
We’ve created this guide to help new contributors understand and navigate the Vuemail codebase.

Top-level directories

After cloning the Vuemail repository you will see a few root-level directories. Here’s a brief overview of each:
DirectoryDescription
appsHere you can find all of the apps related to our online presence, like:
benchmarksWe make benchmarks from version-to-version to demonstrate data-observable performance gains with metrics like p99 and p75.For example, see the Tailwind benchmark, which measures the cost of rendering an email with the Tailwind component.
packagesMost contributions will be made to the packages in this directory.This directory contains all our published NPM packages: the components, the CLI, the render utility, the preview app, the editor and the Nuxt module, alongside shared configuration.
Feel free to open a discussion if you have suggestions on how to better structure these packages to make them more manageable and approachable.

Multiple packages

The Vuemail repository is a pnpm monorepo, which means it contains multiple packages. Because we use pnpm, you will need to use pnpm to install and run each package. If you do not have pnpm installed, follow the pnpm installation guide:
Currently, we have the following packages:

Turborepo

We encourage using turborepo to manage the packages. It’s often helpful to install Turborepo globally to make it easier to run commands in any of the repositories. With a global installation, running turbo build in any of the packages will build both the package you are on as well as the dependent packages. The global installation handles version mismatching as well.

The Vuemail CLI

The CLI is built using commander, a CLI builder for node. It handles parsing of command line arguments and ensuring that the syntax for the command is as expected. The dev, build, start and export commands all depend on the user first installing @vuemaildev/ui, in the same version as @vuemaildev/vuemail. The CLI loads it from the user’s project, and when it’s missing or its version doesn’t match, it asks to install it, using nypm to add it with the user’s package manager.

The Preview Server

The Preview Server, @vuemaildev/ui, has two sides:
  • src/node is the server the CLI starts. It compiles the user’s emails with Vite and @vitejs/plugin-vue, loading them with Vite’s server-side module loader, and renders them with the render utility from the user’s own @vuemaildev/vuemail. Vite’s file watcher detects changes to the files the emails depend on, which the server sends to the browser as server-sent events.
  • src/client is the preview app itself, a Vue 3 single page app built with Vite, vue-router, reka-ui and Tailwind CSS 4.
As changes are sent to the preview app, the preview updates automatically. email build renders every email ahead of time instead, into a static website.

Testing

For testing, we use vitest. We prefer to define globals, and run tests under the happy-dom environment for the code that runs in the browser, like the preview app and the editor, and under node for the rest. We do not strictly enforce testing coverage, but encourage it. For help testing, see our Development workflow guide.
The tests of @vuemaildev/nuxt build and start real Nuxt apps, so they take longer than the others.

Linting

We use biomejs for linting and formatting. Both the linting and formatting are ensured by our GitHub CI so make sure you lint and format your code (pnpm lint:fix) before opening a PR or asking for a review on it. For help linting and formatting, see our Development workflow guide on linting.

Building

We use tsdown to build the packages. The preview app of @vuemaildev/ui is built with Vite. For help building packages, see our Development workflow guide.
Building each package runs tsdown with the settings in its tsdown.config.ts. @vuemaildev/vuemail and @vuemaildev/render are built as both ESM and CJS, along with the type definitions exported from the entry point, src/index.ts, without bundling vue, which can cause issues.

Why build before publishing?

We build the packages before publishing for a few reasons:
  1. All the exported types can be imported from the same place the JavaScript is imported
  2. We have proper CommonJS and ES Modules support
  3. Code that isn’t exported is not published or downloaded