@bitfocusas/email
v0.3.0
Published
Shared design and templates for Bitfocus transactional emails
Keywords
Readme
@bitfocusas/email
Design and templates for Bitfocus transactional emails: the automatic emails our services send (account verification, password resets, sign-in codes, invitations, notifications). Change the look here and every app picks it up on its next version bump.
Not for personal or hand-written mail: email signatures, Gmail replies and the careers mailbox stay as they are.
Used by
| App | Templates |
| ------------------ | --------------------------------------------------------------------------------------------------------- |
| bitfocus-api | verifyEmail, resetPassword, reactivateAccount, subscriptionConfirmed, newsletterVerify |
| bitfocus-auth | signInCode, smtpTest |
| bitfocus-developer | moduleSubmitted, modulePublished, moduleBuildFailed, moduleChangesRequested, discussionActivity |
| bitfocus-intra | productAlert |
| bitfocus-reseller | invitation, memberAdded, notificationDigest |
Usage
Every template returns { subject, html, text }. The caller doesn't need React.
import { verifyEmail } from "@bitfocusas/email";
const { subject, html, text } = await verifyEmail({ url });Send it with your existing sender, or with the shared SMTP sender:
import { createSmtpSender } from "@bitfocusas/email/smtp";
const sender = createSmtpSender({
host: env.SMTP_HOST,
port: env.SMTP_PORT,
username: env.SMTP_USER,
password: env.SMTP_PASS,
from: { name: "Bitfocus", address: "[email protected]" },
});
const outcome = await sender.send({ to, subject, html, text });
// "accepted" | "confirmed-failure" (safe to retry) | "indeterminate" (may have been delivered)The sender always verifies TLS certificates, allows unencrypted SMTP only to localhost, and gives up after 20 seconds.
One-off emails
Apps using React can build their own email from the same parts:
import { Buttons, Heading, Layout, renderEmail, Text } from "@bitfocusas/email/components";
const email = await renderEmail(
"Subject",
<Layout preview="Inbox preview line">
<Heading>Hello</Heading>
<Text>Body</Text>
<Buttons buttons={[{ text: "Open", url }]} />
</Layout>,
);If the email is likely to be reused, or a second app needs it, add it as a template here instead.
Brand overrides
Every template takes an optional brand, e.g. to point the logo at a staging host:
await verifyEmail({ url, brand: { logoUrl: "https://staging.example/logo.png" } });Installing
yarn add @bitfocusas/emailIt's a public package on npm, like @bitfocusas/audit, so installs need no tokens in CI or Docker. The templates only hold copy and brand colours, nothing secret.
The package has no dependencies of its own (about 2 MB installed). Templates ship with React and the renderer bundled in, so backends without React need nothing else. Two entry points use what the app already has:
@bitfocusas/email/smtpneedsnodemailer10.@bitfocusas/email/componentsneedsreactandreact-dom19. It renders the app's own components, which have to share the app's React.
Let Dependabot open a PR in each app when a new version is out:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule: { interval: daily }
allow:
- dependency-name: "@bitfocusas/email"Development
yarn install
yarn dev # live preview of every template at http://localhost:3030
yarn test # snapshot tests; run `yarn test -u` after an intended design change
yarn export # static HTML of every template in out/src/brand.tsholds colours, fonts, logo URL and company details.src/componentsholds the layout and building blocks.src/templates/<app>/<name>.tsxholds one template per file: a React component (default export, used by the preview) and a render function (named export, used by the apps).
Every design change shows up as a diff in tests/__snapshots__. To see it rendered, run yarn dev, or download the email-previews artifact from the PR's CI run.
Adding a template
- Create
src/templates/<app>/<name>.tsxfollowing an existing one, with realistic preview props. - Export its render function from
src/index.ts. - Add it to the list in
tests/templates.test.tsxand runyarn test -u.
Releasing
Run yarn release on an up-to-date main. It runs the checks, asks for the new version, then commits, tags and pushes. The pushed tag triggers the release workflow, which stages the version on npm using trusted publishing, so the repo needs no npm token. The version goes live once a maintainer approves it with 2FA, under the package's Staged Packages tab on npmjs.com or with npm stage list @bitfocusas/email and npm stage approve <stage-id> (staged publishing).
React, react-dom and react-email are bundled into dist, so apps get their security fixes only through a new release here. Dependabot groups them in one bundled PR: merge it and release.
The first release has to be published by hand (corepack yarn build && npm publish --ignore-scripts), because trusted publishing can only be configured for a package that already exists. Then, on npmjs.com, add bitfocusas/bitfocus-email with workflow release.yml as a trusted publisher, leave "Allow npm publish" and "Allow npm dist-tag" unchecked so it can only stage, and set publishing access to disallow bypass 2FA tokens.
Images
Email images are hosted by bitfocus-www at https://bitfocus.io/email/ (its public/email/ folder), so every app shares one stable URL. That folder is the only copy. To change an image, add it there under a new filename and update src/brand.ts. Never overwrite or delete a hosted file, because old emails still load it.
