Skip to content
All docs pages

Next.js

Send from the server, never from the browser: an API key must never reach a page.

Terminal
npm install @heraldmail/sdk @react-email/render

Send from a server action

app/actions.tsx
'use server'

import { Herald } from '@heraldmail/sdk'
import { Welcome } from '@/emails/welcome'

const herald = new Herald(process.env.HERALD_API_KEY)

export async function welcome(userId: string, email: string, name: string) {
  const { error } = await herald.emails.send(
    {
      from: 'Mentor <hello@mentor.app>',
      to: email,
      subject: 'Welcome to Mentor',
      react: <Welcome name={name} />,
    },
    { idempotencyKey: `welcome/${userId}` },
  )
  if (error) console.error('Herald refused the welcome email', error.name, error.message)
}

react takes a React Email element. The SDK renders it to HTML and plain text in your app, with @react-email/render, before sending: your components never run on Herald’s servers.

Take webhooks in a route handler

Verify the signature over the raw body, before you parse it, or anyone could post you events.

app/api/webhooks/herald/route.ts
import { verifyWebhook, WebhookVerificationError } from '@heraldmail/sdk'

export async function POST(request: Request) {
  const body = await request.text()
  try {
    const event = await verifyWebhook<{ type: string; data: { email_id: string } }>(
      body,
      request.headers,
      process.env.HERALD_WEBHOOK_SECRET!,
    )
    if (event.type === 'email.bounced') {
      // mark the address as bad in your own records
    }
    return new Response(null, { status: 204 })
  } catch (err) {
    if (err instanceof WebhookVerificationError) return new Response(null, { status: 400 })
    throw err
  }
}

Answer with a 2xx within 10 seconds; anything else is retried. See Webhooks for every event and what it carries.

Environment

.env
HERALD_API_KEY=hrld_live_…
HERALD_WEBHOOK_SECRET=whsec_…

Keep both server-side only: never give either a NEXT_PUBLIC_ name.