Node.js (18+)

fetch is available globally from Node 18. No polyfill needed.

import { MaildenoClient } from "maildeno"

const client = new MaildenoClient({ apiKey: process.env.MAILDENO_API_KEY! })

Node.js (< 18)

npm install node-fetch
import fetch from "node-fetch"
;(globalThis as any).fetch = fetch
import { MaildenoClient } from "maildeno"

Next.js — App Router (server component or route handler)

// app/api/email/route.ts
import { MaildenoClient } from "maildeno"

const client = new MaildenoClient({
  apiKey: process.env.MAILDENO_API_KEY!,
})

export async function POST(req: Request) {
  const { templateId, name, plan } = await req.json()

  const html = await client.renderHtml(templateId, {
    merge_tags: { text: { name } },
    context:    { plan },
  })

  return Response.json({ html })
}
Instantiate the client at module level (outside the handler) so it is reused across requests within the same Edge / serverless instance.

Next.js — Pages Router (API route)

// pages/api/render-email.ts
import type { NextApiRequest, NextApiResponse } from "next"
import { MaildenoClient, MaildenoError } from "maildeno"

const maildeno = new MaildenoClient({ apiKey: process.env.MAILDENO_API_KEY! })

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { templateId, name, plan } = req.body

  try {
    const html = await maildeno.renderHtml(templateId, {
      merge_tags: { text: { name } },
      context:    { plan },
    })
    res.json({ html })
  } catch (err) {
    if (err instanceof MaildenoError) {
      return res.status(err.status || 500).json({ error: err.code, message: err.message })
    }
    res.status(500).json({ error: "INTERNAL_ERROR" })
  }
}

Express

// server.ts
import express from "express"
import { MaildenoClient, MaildenoError } from "maildeno"

const app = express()
app.use(express.json())

const maildeno = new MaildenoClient({ apiKey: process.env.MAILDENO_API_KEY! })

app.post("/api/render-email", async (req, res) => {
  const { templateId, name, plan } = req.body

  try {
    const html = await maildeno.renderHtml(templateId, {
      merge_tags: { text: { name } },
      context:    { plan },
    })
    res.json({ html })
  } catch (err) {
    if (err instanceof MaildenoError) {
      return res.status(err.status || 500).json({
        error: err.code,
        message: err.message,
      })
    }
    res.status(500).json({ error: "INTERNAL_ERROR" })
  }
})

app.listen(3300, () => console.log("API listening on :3300"))

Nuxt 3 (server route)

// server/api/render.post.ts
import { MaildenoClient } from "maildeno"

export default defineEventHandler(async (event) => {
  const { templateId, dynamicData } = await readBody(event)
  const config = useRuntimeConfig()

  const client = new MaildenoClient({ apiKey: config.maildenoApiKey })

  return client.render({ templateId, target: "html", dynamicData })
})

Cloudflare Workers

// worker.ts
import { MaildenoClient } from "maildeno"

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const { templateId, name } = await request.json() as any

    const client = new MaildenoClient({ apiKey: env.MAILDENO_API_KEY })

    const html = await client.renderHtml(templateId, {
      merge_tags: { text: { name } },
    })

    return new Response(html, { headers: { "Content-Type": "text/html" } })
  },
}

Store MAILDENO_API_KEY as a Cloudflare Workers secret.

Frontend / browser usage

Never expose your API key in browser code.

For browser-initiated email renders, proxy through a server endpoint:

// In your browser code — calls your own backend, which holds the key
const response = await fetch("/api/render-email", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ templateId: "...", name: "Noruwa" }),
})
const { html } = await response.json()

Or call the Maildeno REST API directly with the Authorization header from a server:

// Server-side only — do NOT run this in the browser
const response = await fetch("https://api.maildeno.com/v1/sdk/render", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${process.env.MAILDENO_API_KEY}`,
  },
  body: JSON.stringify({
    template_id: "c1a28520-c0ef-41e7-8348-da18fb7769d1",
    target: "html",
    dynamic_data: {
      merge_tags: { text: { name: "Noruwa", company: "Maildeno" } },
      context:    { plan: "standard" },
    },
  }),
})

const html = await response.text()