# Mailing's README stops before the first install command

> Mailing renders React email templates through MJML and previews them without sending. What the repository actually documents is in its root package.json: a reset script that deletes your emails directory, a link script that does nothing, and two test stacks.

**sofn-xyz/mailing** — Build, test, send emails with React

- Repository: https://github.com/sofn-xyz/mailing
- Website: https://www.mailing.run
- Stars: 3,604 · Forks: 70
- Language: TypeScript
- License: MIT
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/sofn-xyz-mailing

## The README stops before the first install command

The description is four words long, Build, test, send emails with React, and the file under it never turns into a setup guide. There is no install line, no create command, and no list of configuration keys anywhere in it. Setup is one sentence and one link: check out the official docs at www.mailing.run/docs. What the file does fix is the floor. Mailing requires version 16+ of node and Mac or Linux, and Windows is stated as not supported at this time, with a pull request welcome against issue 187. The feature list is more specific than the setup section: MJML components that work across clients including Outlook, a preview server with live reload, dev mode that opens emails in the browser instead of sending, a test mode for checking that emails send with the correct content, and support for frameworks such as next.js and remix. The reader gets promises about behaviour and a pointer about installation, with nothing in between.

## dev:init deletes the emails directory and the config file

The root package.json is where the actual behaviour of this repository shows up, and the first script worth reading is the reset. dev:init does not initialise anything:

```json
"dev:init": "cd packages/cli && rm -rf previews_html; rm -rf .mailing emails mailing.config.json; src/dev.js"
```

Three removals run in sequence before the dev entry point starts, and two of the targets, `emails` and `mailing.config.json`, are named the way a user's project names things rather than the way this monorepo names them. The reset is therefore aimed at the directory you invoke it from, and it deletes the folder holding the email components along with the file holding the config, with no prompt and no copy step. The neighbouring scripts are narrower: dev:export-previews removes only `.mailing` before calling `src/dev.js export-previews`, and dev:server:build removes only `.mailing` before calling `src/dev.js server build`.

## link:emails prints noop and two scripts still call it

One script is worth a second look because it does nothing at all:

```json
"link:emails": "echo 'noop'"
```

Linking a package is the step that points the CLI at the email sources in a working project, and here that step is a placeholder that prints a single word. The stub has not been removed from its callers either. dev:export-previews still begins with `yarn link:emails`, so the export path runs a no-op, then deletes `.mailing`, then calls `src/dev.js export-previews`. Reading the manifest to understand how the pieces connect is the normal way to work out what a monorepo expects from you, and a script named link that only echoes leaves that question open: whether the preview export works without the link step, or only where the email sources already sit in the expected place. The sibling workspace entry points are explicit about their targets, dev goes to packages/cli and dev:web goes to packages/web.

## End to end tests run through Ruby in a TypeScript monorepo

A TypeScript workspace with a Ruby toolchain in the root is the next surprise. The tree carries Gemfile, Gemfile.lock, .rubocop.yml and .tool-versions next to yarn.lock, tsconfig.json and babel.config.json, and one script hands the end to end suite to Ruby:

```bash
bundle && bundle exec ruby e2e/cli.rb run-all
```

The e2e directory sits at the root beside packages/, and the argument it receives is run-all, so the Ruby side is a harness driving the JavaScript workspaces rather than part of the shipped library. The npm packages are built with preconstruct, and the split between the two working directories is visible in the script names as well as the paths. Contributing to the project therefore means having Ruby and Bundler available to run the whole suite, and the README's prerequisite sentence covers node and the operating system without mentioning either of those.

## Three Jest configs, Cypress on top, and a port check first

Testing is layered past a single jest run. The root holds jest.config.ts and jest.integration.config.ts, plus jsdom.env.ts, testSetup.ts, testSetup.integration.ts and testUtilIntegration.ts, and the integration scripts choose between them with a $TEST_FLAGS passthrough. Browser level coverage sits above that: test:integration:cypress and its open variant run cypress against cypressIntegration.config.ts from inside packages/cli. Every integration entry point is wrapped in a server start step, and the manifest uses two different names for it, with test:integration calling integration:servers:start and ci:test calling test:servers:start. That step begins by running ./scripts/assert-free-ports, so ports are checked before anything boots, which is why the check exists as a script of its own rather than as part of jest. Anyone reproducing a failure locally has to follow the same order: free ports, test databases, API and web servers, then the tests.

## Test servers point at a literal testApiKey on port 3883

Here is that server start step as the manifest spells it:

```bash
./scripts/assert-free-ports && MAILING_API_KEY=testApiKey MAILING_API_URL=http://localhost:3883
```

The API key is a literal placeholder string and the API URL points at localhost on port 3883, so the test stack talks to a local server as long as those values stay in the script. The file that documents the real variables is .env.example, and it splits platform settings from test settings:

```env
MAILING_DATABASE_URL=
MAILING_SESSION_PASSWORD=
MAILING_SES_USER=
MAILING_SES_PASSWORD=
```

MAILING_SESSION_PASSWORD carries an inline note asking for a 32 character password for iron-session, and the closing line of the file says the same value must be set for integration tests to run at all. Sending is configured through MAILING_SES_USER and MAILING_SES_PASSWORD, so the SES side is where a real credential enters this picture. Two more variables are reserved for the test run, MAILING_DATABASE_URL_TEST and WEB_DATABASE_URL_TEST, and WEB_DATABASE_URL is asked for separately because packages/web keeps its own database.

## No GitHub releases, and a private root manifest at 0.1.0

The versioning picture is thin at the root. The manifest in this repository is named mailing-monorepo, marked private, and sits at version 0.1.0 with packages/* as workspaces, so the number a reader sees belongs to the workspace root rather than to the package published to npm, and the tree has no GitHub releases to consult instead. What it does offer is the usual changelog machinery: a .changeset directory, a .husky directory, and a .lintstagedrc next to .eslintrc.json and .eslintignore, the shape of a project that writes its changelog as pending changeset files rather than as tags. The same split shows in the counters, where 3605 stars sit against 70 forks and 58 open issues. That gap fits a project whose deliverable is a package people install rather than a codebase they fork to patch. The last commit on the default branch main is dated 2026-07-10.

## Conclusion

Mailing fits teams already writing React components who want templates, a live preview and a browser test path, and the missing setup section is a documentation gap rather than a design flaw, since the docs site carries those steps. Check three things before adopting it: Windows is unsupported today, the reset script removes the emails directory and mailing.config.json, and the version you install is not the 0.1.0 written in the monorepo manifest. Skip it if your templates are not React components.

## FAQ

### Does Mailing support Windows?

Not at this time. The README states that Mailing requires version 16+ of node and running on Mac or Linux, and says Windows is not supported, with a pull request welcome to fix the bugs tracked in issue 187.

### How do you install the Mailing package?

The README has no install command of its own and sends you to the official docs at https://www.mailing.run/docs. The package it links on npm is named mailing, and the examples it points to live under the templates section of those docs.

### What environment variables does Mailing expect?

From .env.example: MAILING_DATABASE_URL, MAILING_SESSION_PASSWORD as a 32 character password for iron-session, MAILING_SES_USER and MAILING_SES_PASSWORD for sending, WEB_DATABASE_URL for packages/web, and MAILING_DATABASE_URL_TEST with WEB_DATABASE_URL_TEST for integration tests.

### What happens to emails in Mailing dev mode?

Dev mode opens emails in the browser instead of sending them. Alongside it there is a preview server with live reload, a mobile toggle with hotkeys, and a test mode for checking that emails send and have the correct content.

### Does the Mailing repository publish GitHub releases?

No. It has no GitHub releases. The root package.json is a private workspace named mailing-monorepo at version 0.1.0, and pending changelog entries are kept in the .changeset directory.

## Sources

- [Issues](https://github.com/sofn-xyz/mailing/issues)
- [License: MIT](https://github.com/sofn-xyz/mailing/blob/main/LICENSE)
- [Project website](https://www.mailing.run)
- [README](https://github.com/sofn-xyz/mailing/blob/main/README.md)
- [sofn-xyz/mailing on GitHub](https://github.com/sofn-xyz/mailing)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/sofn-xyz-mailing
