backstro-email

Use when creating HTML email templates with Astro components - welcome emails, password resets, notifications, order confirmations, newsletters, or transactional emails using the @backstro/email library.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "backstro-email" with this command: npx skills add backstrojs/email/backstrojs-email-backstro-email

Backstro Email

Build and send HTML emails using Astro components - a modern, component-based approach to email development that works across all major email clients.

Installation

Install the package in your existing project:

npm install @backstro/email
pnpm add @backstro/email
yarn add @backstro/email

Basic Email Template

Create a .astro file in your emails/ folder:

---
// emails/WelcomeEmail.astro
import { Html, Head, Body, Container, Preview, Heading, Text, Button } from '@backstro/email';

interface Props {
  name: string;
  verificationUrl: string;
}

const { name, verificationUrl } = Astro.props;
---

<Html lang="en">
  <Head />
  <Body style={{ backgroundColor: '#f3f4f6', fontFamily: 'sans-serif' }}>
    <Preview>Welcome – Verify your email</Preview>
    <Container>
      <Heading as="h1">Welcome!</Heading>
      <Text>Hi {name}, thanks for signing up!</Text>
      <Button href={verificationUrl}>Verify Email</Button>
    </Container>
  </Body>
</Html>

Key differences from React Email:

  • Files are .astro, not .tsx
  • Props are accessed via Astro.props, not function parameters
  • Define props with interface Props in the frontmatter (--- block)
  • Conditionals and loops use Astro template expressions: {condition && <Component />}, {items.map(item => <Component />)}
  • No JSX — Astro template syntax
  • No PreviewProps — pass props at render time

Essential Components

See references/COMPONENTS.md for complete component documentation.

Core Structure:

  • Html - Root wrapper with lang attribute
  • Head - Meta elements, styles, fonts
  • Body - Main content wrapper (Yahoo/AOL compat table included)
  • Container - Centers content (max-width layout)
  • Preview - Inbox preview text
  • Section - Layout sections
  • Row & Column - Multi-column layouts

Content:

  • Heading - h1–h6 via as prop; supports margin shorthands (m, mx, my, mt, mr, mb, ml)
  • Text - Paragraphs
  • Button - Styled link buttons (MSO conditional comments for Outlook padding)
  • Link - Hyperlinks
  • Img - Images
  • Hr - Horizontal dividers

Specialized:

  • CodeBlock - Syntax-highlighted code (Prism.js themes)
  • CodeInline - Inline code (Orange.fr compatible)
  • Markdown - Render markdown with inline styles
  • Font - Custom web fonts via @font-face

Before Writing Code

When a user requests an email template, ask clarifying questions FIRST if they haven't provided:

  1. Brand colors - Ask for primary brand color (hex code like #007bff)
  2. Logo - Ask if they have a logo file and its format (PNG/JPG only - warn if SVG/WEBP)
  3. Style preference - Professional, casual, or minimal tone
  4. Production URL - Where will static assets be hosted in production?

Static Files and Images

Local images must be placed in the public/ or static/ folder and served as absolute URLs in production.

project/
├── emails/
│   └── WelcomeEmail.astro
└── public/
    └── logo.png          <-- images go here

Use import.meta.env for environment-specific URLs:

---
const baseURL = import.meta.env.PROD
  ? 'https://cdn.example.com'
  : '';
---

<Img src={`${baseURL}/logo.png`} alt="Logo" width="150" height="50" />

Loops and Conditionals

Because email templates are Astro components, native Astro template syntax works:

---
interface Props {
  items: Array<{ name: string; qty: number; price: number }>;
  isPremium: boolean;
}
const { items, isPremium } = Astro.props;
---

{/* Conditional */}
{isPremium && (
  <Section>
    <Text style={{ color: '#f59e0b', fontWeight: 'bold' }}>Premium member</Text>
  </Section>
)}

{/* Loop */}
{items.map((item) => (
  <Row>
    <Column><Text>{item.name}</Text></Column>
    <Column><Text>{item.qty}</Text></Column>
    <Column><Text>${(item.qty * item.price).toFixed(2)}</Text></Column>
  </Row>
))}

Styling

See references/STYLING.md for complete styling documentation.

Use inline styles (via the style prop, which accepts a JS object) for maximum email client compatibility:

<Text style={{ color: '#374151', fontSize: '14px', lineHeight: '24px' }}>
  Hello world
</Text>

Tailwind CSS (optional)

Install tailwindcss and use class names. Run inlineTailwind() as a post-processing step after rendering:

import { render } from '@backstro/email/render';
import WelcomeEmail from './emails/WelcomeEmail.astro';

const html = await render(WelcomeEmail, { name: 'Alice' }, { tailwind: {} });

When using Tailwind:

  • Use utility classes via class="..." on components
  • No <Tailwind> wrapper component needed — inlining is done post-render
  • Email client limitations still apply (see references/STYLING.md)

Email Client Limitations

  • Never use SVG or WEBP — warn users about rendering issues
  • Never use flexbox — use Row/Column components for layouts
  • Never use CSS media queries — not reliably supported in email clients
  • Always specify explicit border styles on <Hr> and bordered elements
  • Border single-side: include a reset (e.g. borderTop: 'none') for other sides

Rendering

Convert to HTML

import { render } from '@backstro/email/render';
import WelcomeEmail from './emails/WelcomeEmail.astro';

const html = await render(WelcomeEmail, { name: 'Alice', verificationUrl: 'https://example.com/verify' });

Convert to Plain Text

import { renderText } from '@backstro/email/render';
import WelcomeEmail from './emails/WelcomeEmail.astro';

const text = await renderText(WelcomeEmail, { name: 'Alice', verificationUrl: 'https://example.com/verify' });

With Tailwind

const html = await render(WelcomeEmail, props, { tailwind: {} });

// With custom Tailwind config:
const html = await render(WelcomeEmail, props, {
  tailwind: {
    theme: {
      extend: {
        colors: { brand: '#0070f3' },
      },
    },
  },
});

Sending

See references/SENDING.md for provider guides.

Quick example using Resend:

import { render } from '@backstro/email/render';
import { Resend } from 'resend';
import WelcomeEmail from './emails/WelcomeEmail.astro';

const resend = new Resend(process.env.RESEND_API_KEY);

const html = await render(WelcomeEmail, { name: 'Alice', verificationUrl: 'https://example.com/verify' });
const text = await renderText(WelcomeEmail, { name: 'Alice', verificationUrl: 'https://example.com/verify' });

const { data, error } = await resend.emails.send({
  from: 'Acme <onboarding@resend.dev>',
  to: ['user@example.com'],
  subject: 'Welcome to Acme',
  html,
  text,
});

if (error) {
  console.error('Failed to send:', error);
}

Internationalization

See references/I18N.md for complete i18n documentation.

Quick example using a locale prop pattern:

---
interface Props {
  name: string;
  locale: 'en' | 'es' | 'fr';
}

const { name, locale } = Astro.props;

const messages = {
  en: { greeting: 'Hi', cta: 'Get Started' },
  es: { greeting: 'Hola', cta: 'Comenzar' },
  fr: { greeting: 'Bonjour', cta: 'Commencer' },
};

const t = messages[locale];
---

<Html lang={locale}>
  <Head />
  <Body>
    <Container>
      <Text>{t.greeting} {name},</Text>
      <Button href="https://example.com">{t.cta}</Button>
    </Container>
  </Body>
</Html>

Behavioral Guidelines

  • When re-iterating over code, only update what the user asked for; keep the rest intact
  • Never use template variables (like {{name}}) directly in Astro expressions — reference props directly ({name})
  • If a user explicitly wants {{name}} as a literal string (for use with external templating), use it only as a default prop value at render time, never inside the template itself
  • If the user asks to use media queries in CSS, inform them email client support is limited and suggest inline conditional layouts instead
  • Always use interface Props in the frontmatter to type component props
  • Component imports must use the full package path from @backstro/email

Cloudflare Workers

Astro email works in Cloudflare Workers with the nodejs_compat flag — AstroContainer requires Node stream polyfills.

# wrangler.toml
compatibility_flags = ["nodejs_compat"]

For Tailwind inlining in Workers, pass the CSS as a string (avoids fs.readFile):

import tailwindCss from 'tailwindcss/index.css?raw'; // bundled by wrangler

const html = await render(MyEmail, props, {
  tailwind: { cssSource: tailwindCss },
});

Common Patterns

See references/PATTERNS.md for complete examples including:

  • Password reset emails
  • Order confirmations with product lists
  • Notifications with code blocks
  • Multi-column layouts
  • Custom fonts

Additional Resources

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

Microsoft 365 MCP Server

Integrate Microsoft 365 to manage Outlook email, calendar events, OneDrive files, Tasks, Teams chats, and user profiles via Microsoft Graph and MCP protocol.

Registry SourceRecently Updated
42.5K
Profile unavailable
General

Smart Email

Email assistant skill — check emails, AI summaries, daily digests. Supports Gmail, Outlook/M365, Google Workspace. Users interact through their chat platform...

Registry SourceRecently Updated
090
Profile unavailable
General

Skulk Email

Email via DreamHost — read inbox, send email, search messages. Send works from any VPS (including DigitalOcean) by routing through DreamHost's Roundcube webm...

Registry SourceRecently Updated
082
Profile unavailable