DEV Community

Cover image for Ansomail Devlog #1: Replacing JavaScript with Rust for 15x Faster Email Rendering (Next.js + WASM)
Sanjay Kumar Sah
Sanjay Kumar Sah Subscriber

Posted on

Ansomail Devlog #1: Replacing JavaScript with Rust for 15x Faster Email Rendering (Next.js + WASM)

Ansomail is an AI-powered drag-and-drop email builder I’m building in public. This week, I replaced its JavaScript rendering engine with Rust — and made it 15x faster.

I decided to do something scary: build a SaaS product in public, from scratch, sharing every win and every embarrassing error log along the way.

By Day 4, I had:

  • Crashed my database
  • Broken hot reload
  • Built an engine that felt painfully slow

By Day 7, I had:

  • Rewritten the rendering engine in Rust
  • Reduced render time from ~18ms → ~1.1ms (~15x faster)
  • Learned that “bleeding edge” stacks come with hidden costs

This is my Week 1 retrospective building Ansomail, an AI-powered drag-and-drop email editor.


The Goal: Building a High-Performance AI Email Builder

I’m building an email editor where:

  • Developers define the design system
  • Marketers drag-and-drop blocks
  • AI generates content — but never breaks layout

The constraint: The preview must feel like Google Docs. Instant.


Tech Stack: Next.js 16, Bun, Postgres & MJML

I deliberately chose a modern stack optimized for speed:

  • Runtime: Bun
  • Framework: Next.js 16 (Turbopack)
  • Monorepo: Turborepo
  • Database: Postgres + Drizzle ORM
  • Linting/Formatting: Biome
  • Initial Rendering Engine: mjml-browser

It looked perfect on paper.

Then reality happened.


Days 1–2: Enforcing AI Output with JSON Schema Validation

The first architectural decision wasn’t about performance.

It was about control.

Instead of letting the LLM freely generate HTML, I:

  • Forced output into a strict JSON Schema
  • Validated structure before rendering
  • Rejected hallucinated classes or invalid styles

If the AI generates something outside the system, the update is rejected before it ever touches the UI.

That constraint will save me months later.


Days 3–4: The “Bleeding Edge” Tax (Next.js + Bun Issues)

Using Bun with Next.js 16 is fast — but not always stable.

Hot reload started crashing with cache component errors:

Next.js cannot guarantee that Cache Components will run as expected due to the current runtime's implementation of setTimeout().
Enter fullscreen mode Exit fullscreen mode

Then I hit this:

FATAL: sorry, too many clients already
Enter fullscreen mode Exit fullscreen mode

In a serverless dev environment, every hot reload opened a new Postgres connection.

They weren’t being closed. My database choked.


Fixing “Too Many Clients Already” with Proper Connection Pooling

“Serverless” does NOT mean “Ops-less.”

I implemented strict pooling and ensured a singleton pattern during development:

import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";

import * as schema from "./schema";

const connectionString = process.env.DATABASE_URL;

if (!connectionString) {
  throw new Error("DATABASE_URL is missing");
}

const globalForDb = globalThis as unknown as {
  pool: Pool | undefined;
};

const pool =
  globalForDb.pool ??
  new Pool({
    connectionString,
    max: process.env.DB_MAX_CONNECTIONS
      ? parseInt(process.env.DB_MAX_CONNECTIONS, 10)
      : 10,
    idleTimeoutMillis: 30000,
    connectionTimeoutMillis: 2000,
  });

if (process.env.NODE_ENV !== "production") {
  globalForDb.pool = pool;

  pool.on("connect", (client) => {
    console.log(`🔌 Database: New client connected to the pool; client: ${client}`);
  });

  pool.on("error", (err, client) => {
    console.error(`❌ Database: Unexpected error on idle client; client: ${client}`, err);
  });

  pool.on("remove", (client) => {
    console.log(`🗑️ Database: Client removed from pool; client: ${client}`);
  });
}

export const db = drizzle(pool, { schema });
Enter fullscreen mode Exit fullscreen mode

Once I capped connections, everything stabilized.

Lesson: Dev hot reload + serverless Postgres can quietly DOS your own app.


Days 5–6: Implementing Optimistic UI for Instant Feedback

I implemented Optimistic UI for renaming templates.

User types → UI updates immediately.
Server validates in the background.
Failure → rollback.
Success → seamless.

That part worked beautifully.

But the real bottleneck was rendering.

Initial Rendering Pipeline (JavaScript + MJML)

The original rendering pipeline:

  1. JSON stored in DB
  2. JSON → MJML
  3. mjml-browser converts MJML → HTML
  4. HTML rendered inside an iframe

It worked.

But it averaged ~18ms per render.

For a drag-and-drop email builder, that felt sluggish.


Day 7: Replacing JavaScript with Rust + WebAssembly

The bottleneck was mjml-browser (pure JavaScript).

So I replaced it.

I integrated MRML (a Rust port of MJML) compiled to WebAssembly (WASM).

Why?

  • Rust → predictable performance
  • MRML → faster MJML parsing
  • WASM → near-native speed in the browser

Rust Integration Challenges

Rust does not tolerate “almost correct.”

It refused to compile my “sloppy” JSON that JavaScript had been happily ignoring.

It forced me to fix my data structures.

That strictness improved my architecture.


Performance Results: 15x Faster Email Rendering

After integrating MRML via WASM:

  • JavaScript Engine: ~18ms
  • Rust (WASM) Engine: ~1.1ms

That’s roughly a 15x performance improvement.

The preview now runs comfortably within a 16ms frame budget — effectively real-time at 60fps.


Unexpected Benefits of Moving to Rust

  1. Cleaner schema enforcement
  2. Deterministic parsing
  3. Smaller rendering bottleneck surface
  4. Future-proof performance foundation

Lessons Learned Building a SaaS in Public (Week 1)

  1. Bleeding-edge stacks save time — until they don’t.
  2. Serverless still requires backend discipline.
  3. Rust’s strictness is a feature, not friction.
  4. Early performance decisions compound.

What’s Next for Ansomail?

Week 1 was about the foundation.

Week 2 is about interaction.

Now that I have a ~1ms rendering engine, I’m building the actual drag-and-drop layer on top of it.

I’m documenting this entire journey publicly.

If you're building with Next.js, Rust, WebAssembly, or experimenting with high-performance SaaS architecture — I’d love to connect.

Ansomail is currently in development. If you're a developer or marketer who struggles with email design systems, I’d love to talk.

Top comments (0)