A Svelte preprocessor that transforms Tailwind CSS classes in email components to inline styles with responsive media query support.
⨠Stable & Future-Proof - Uses Svelte's public preprocessor API
šØ Tailwind CSS Support - Transforms Tailwind classes to inline styles for email clients
š± Responsive Emails - Preserves responsive classes (sm:
, md:
, lg:
) as media queries
ā” Build-Time Transformation - Zero runtime overhead
š TypeScript First - Fully typed with comprehensive type definitions
ā
Well Tested - Extensive test coverage with unit and integration tests
Email clients don't support modern CSS in <style>
tags, requiring inline styles. But writing inline styles is tedious and hard to maintain. This preprocessor lets you write Tailwind CSS classes and automatically transforms them to inline styles at build time.
npm install better-svelte-email
# or
bun add better-svelte-email
# or
pnpm add better-svelte-email
Add the preprocessor to your svelte.config.js
:
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import { betterSvelteEmailPreprocessor } from 'better-svelte-email';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: [
vitePreprocess(),
betterSvelteEmailPreprocessor({
pathToEmailFolder: '/src/lib/emails',
debug: false
})
],
kit: {
adapter: adapter()
}
};
export default config;
Create your email templates in src/lib/emails/
:
<!-- src/lib/emails/welcome.svelte -->
<script>
let { name = 'User' } = $props();
</script>
<Html>
<Head />
<Body class="bg-gray-100">
<Container class="mx-auto p-8">
<Text class="mb-4 text-2xl font-bold">
Welcome, {name}!
</Text>
<Button
href="https://example.com"
class="rounded bg-blue-600 px-6 py-3 text-white sm:bg-green-600"
>
Get Started
</Button>
</Container>
</Body>
</Html>
// src/routes/api/send-email/+server.ts
import { render } from 'svelte/server';
import WelcomeEmail from '$lib/emails/welcome.svelte';
export async function POST({ request }) {
const { name, email } = await request.json();
// Render email (preprocessor already ran at build time!)
const result = render(WelcomeEmail, { props: { name } });
// Send email using your preferred service (Resend, SendGrid, etc.)
// await resend.emails.send({
// from: '[email protected]',
// to: email,
// subject: 'Welcome!',
// html: result.body
// });
return new Response('Sent');
}
The preprocessor transforms your Tailwind classes in three steps:
<!-- Input -->
<Button class="bg-blue-500 p-4 text-white">Click</Button>
<!-- Output -->
<Button
styleString="background-color: rgb(59, 130, 246); color: rgb(255, 255, 255); padding: 16px;"
>
Click
</Button>
<!-- Input -->
<Button class="bg-blue-500 sm:bg-red-500">Click</Button>
<!-- Output -->
<Button class="sm_bg-red-500" styleString="background-color: rgb(59, 130, 246);">Click</Button>
<!-- Injected into <Head> -->
<style>
@media (max-width: 475px) {
.sm_bg-red-500 {
background-color: rgb(239, 68, 68) !important;
}
}
</style>
<!-- Input -->
<Button class="rounded bg-blue-500 p-4 sm:bg-red-500 md:p-6">Click</Button>
<!-- Output -->
<Button
class="sm_bg-red-500 md_p-6"
styleString="border-radius: 4px; background-color: rgb(59, 130, 246); padding: 16px;"
>
Click
</Button>
interface PreprocessorOptions {
/**
* Path to folder containing email components
* @default '/src/lib/emails'
*/
pathToEmailFolder?: string;
/**
* Custom Tailwind configuration
* @default undefined
*/
tailwindConfig?: TailwindConfig;
/**
* Enable debug logging
* @default false
*/
debug?: boolean;
}
betterSvelteEmailPreprocessor({
pathToEmailFolder: '/src/lib/emails',
tailwindConfig: {
theme: {
extend: {
colors: {
brand: '#FF3E00'
}
}
}
}
});
sm:
, md:
, lg:
, xl:
, 2xl:
){#if}
){#each}
)class={someVar}
)sm:[color:red]
)import { betterSvelteEmailPreprocessor } from 'better-svelte-email';
For advanced use cases, you can use individual functions:
import {
parseClassAttributes,
transformTailwindClasses,
generateMediaQueries,
injectMediaQueries
} from 'better-svelte-email';
The library includes comprehensive tests:
# Run all tests
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with UI
npm run test:ui
# Run tests with coverage
npm run test:coverage
<Button class="rounded bg-blue-500 px-4 py-2 text-white">Click Me</Button>
<Container class="mx-auto w-full max-w-2xl p-4 sm:p-6 md:p-8">
<Text class="text-lg sm:text-xl md:text-2xl">Responsive Text</Text>
</Container>
<Html>
<Head />
<Body class="bg-gray-100 font-sans">
<Container class="mx-auto my-8 max-w-2xl rounded-lg bg-white shadow-lg">
<Section class="p-8">
<Text class="mb-4 text-3xl font-bold text-gray-900">Welcome to Our Service</Text>
<Text class="mb-6 text-gray-600">
Thank you for signing up. We're excited to have you on board!
</Text>
<Button
href="https://example.com/verify"
class="rounded-lg bg-blue-600 px-8 py-4 font-semibold text-white sm:bg-green-600"
>
Verify Your Email
</Button>
</Section>
</Container>
</Body>
</Html>
pathToEmailFolder
{ debug: true }
<Head />
component in your emailsm:
, md:
, etc.)Make sure you have the latest version of Svelte (requires 5.14.3 or higher):
npm install svelte@latest
Contributions are welcome! Please feel free to submit a Pull Request.
MIT
Konixy
bun test
All tests must pass before pushing to main. The CI/CD pipeline will automatically run tests on every push and pull request.
bun run build
git checkout -b feature/amazing-feature
)bun test
)feat:
- New featuresfix:
- Bug fixesdocs:
- Documentation changestest:
- Test additions/changeschore:
- Maintenance tasksgit push origin feature/amazing-feature
)Releases are automated via GitHub Actions. When you bump the version in package.json
and push to main
, a new release will be automatically created with a generated changelog.
See RELEASE.md for detailed release process documentation.
If you find this project useful, please consider giving it a āļø on GitHub!