Build This Now
Build This Now
Was ist der Claude Code?Claude Code installierenClaude Code Native InstallerDein erstes Claude Code-Projekt
Failed Payment RecoveryLocalization and i18nPDF InvoicesReferral ProgramTwo-Factor AuthNext.js DevTools MCPNext.js Agent SetupSubscription BillingMulti-Tenant SaaSAI Chat FeatureRate LimitingStripe WebhooksSemantic SearchRealtime UpdatesDrip Email Sequencesv2.1.122 Release NotesClaude Code Dynamic Workflows: 1.000 Subagents auf einer echten Codebase orchestrierenClaude Code Best PracticesClaude Opus 4.7 Best PracticesClaude Code auf einem VPSGit-IntegrationClaude Code ReviewClaude Code WorktreesClaude Code Remote ControlClaude Code ChannelsChannels, Routines, Teleport, DispatchGeplante Aufgaben mit Claude CodeClaude Code BerechtigungenClaude Code Auto-ModusStripe-Zahlungen mit Claude Code einbauenFeedback-LoopsTodo-WorkflowsClaude Code TasksProjekt-TemplatesClaude Code Preise und Token-NutzungClaude Code Preise: Was du wirklich zahlstClaude Code Ultra ReviewEine Next.js-App mit Claude Code bauenSupabase DatabaseVercel DeepsecTest-Driven DevelopmentSo baust du ein SaaS-MVP mit Claude CodeAuthentifizierung mit Claude Code einbauen (Supabase Auth)Transaktions-E-Mails mit Claude Code einbauen (Resend + React Email)Eine typsichere API mit Claude Code bauen (oRPC + Zod)File UploadsBackground Jobs (Inngest)Admin DashboardFull-Text SearchClaude Agent SDKKiro Migration GuideClaude Research AgentLeave Grok BuildRoute Subagent ModelsClaude Monorepo SetupCI Repair AgentVisual Regression TestsAgent Cost DashboardCursor Migration GuideAgentic Commerce: Wie du eine App baust, für die KI-Agents bezahlen können1M ContextUser API KeysAudit LogsCSV Import PipelineDatabase MigrationsProduction Error TrackingFeature FlagsGitHub ActionsHeadless ModeMax Plan vs APICaching and RevalidationIn-App NotificationsOutbound WebhooksPrompt CachingRoles & PermissionsMarketplace PaymentsUsage-Based BillingWas es 2026 kostet, ein SaaS mit Claude Code zu bauenParallel AI AgentsCoding Agent Injection
speedy_devvkoen_salo
Blog/Handbook/Workflow/Transactional Email

Transaktions-E-Mails mit Claude Code einbauen (Resend + React Email)

So baust du Willkommens-, Beleg- und Passwort-Reset-E-Mails in einer Next.js-16-App mit Resend und React Email, wobei Claude Code die Templates und die Send-Logik schreibt.

Sie möchten das Framework hinter diesen Projekten?

Holen Sie sich das Claude Code System, mit dem wir produktionsreife Software planen, bauen, testen und ausliefern.

Sehen Sie, was wir für Unternehmen bauen →
speedy_devvkoen_salo
speedy_devvWritten by speedy_devvPublished Jul 14, 20269 min readHandbook hubWorkflow index

Jedes SaaS braucht am ersten Tag drei E-Mails: Willkommen, Beleg, Passwort-Reset. Sie falsch zu machen (im Spam zu landen, einen DKIM-Record zu vergessen, bei einem Tippfehler still zu scheitern) kostet dich Nutzer, bevor die überhaupt das Onboarding beenden. Das ist eine vollständige Anleitung, um Resend und React Email in einer Next.js-16-App mit Claude Code zu verkabeln, von der Domain-Verifizierung bis zu einem funktionierenden Send in einer Server Action.

Warum Transaktions-E-Mail knifflig ist

Transaktions-E-Mail sieht simpel aus, bis du sie tatsächlich auslieferst. Du brauchst eine verifizierte Absender-Domain, sonst gehen deine E-Mails in den Spam oder werden gar nicht verschickt. Du brauchst HTML, das konsistent über Gmail, Outlook und Apple Mail rendert, die wild unterschiedlichen CSS-Support haben. Du musst den Send im richtigen Moment auslösen (nachdem der Signup abgeschlossen ist, nachdem eine Zahlung erfolgreich war), ohne die Response zu blockieren, auf die der Nutzer wartet. Und du musst wissen, wann ein Send tatsächlich fehlgeschlagen ist, statt anzunehmen, dass er geklappt hat.

Resend kümmert sich um Zustellung und Domain-Reputation. React Email kümmert sich um die Templates, sodass du JSX schreibst statt handgetippter Tabellen-Layouts. Claude Code ist hier nützlich, weil es beide APIs schon gut kennt und die Verkabelung (Server Actions, Error-Handling, Environment-Variablen) im ersten Anlauf korrekt hinbekommt, solange du ihm genau sagst, was es bauen soll.

Resend und React Email installieren

Du brauchst drei Pakete: das Resend-SDK zum Versenden, die React-Email-Komponenten-Library zum Bauen der Templates und die React-Email-CLI, um sie lokal zu previewen. Lass Claude Code alle drei installieren, und es nutzt automatisch den Package-Manager deines Projekts.

npm install resend @react-email/components
npm install -D react-email

resend ist der Client, der mit der Resend-API spricht. @react-email/components gibt dir vorgefertigte, E-Mail-sichere Primitives (Html, Body, Container, Button, Text), die korrekt über Mail-Clients rendern, ohne dass du mit Inline-CSS kämpfst. react-email ist eine reine Dev-Dependency, sie treibt den lokalen Preview-Server an, den du später nutzt.

Einen API-Key holen und deine Absender-Domain verifizieren

Registrier dich bei resend.com und erstell einen API-Key über das Dashboard. Speicher ihn als Environment-Variable, hardcode ihn nie in ein Template und committe ihn nie.

# .env.local
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxx
EMAIL_FROM="Your App <hello@yourdomain.com>"

Ohne eine verifizierte Domain lässt Resend dich nur an deine eigene Account-E-Mail senden, über die geteilte onboarding@resend.dev-Adresse. Das reicht für einen ersten Test, aber echte Nutzer bekommen darüber nichts. Um an tatsächliche Postfächer zu senden, geh im Resend-Dashboard zu Domains, füg deine Domain hinzu und trag die SPF- und DKIM-Records, die es dir gibt, bei deinem DNS-Provider ein. Die Verifizierung dauert meist ein paar Minuten, sobald die Records propagiert sind, je nach Registrar gelegentlich länger.

Sag Claude Code, welche Domain du nutzt, und es erinnert dich daran, den Verifizierungsstatus zu prüfen, bevor du einen echten Send testest:

claude "add a startup check that warns in the console if EMAIL_FROM's domain isn't verified in Resend, using the domains.list API"

Ein React-Email-Template bauen

Templates leben in ihrem eigenen Ordner, damit der Preview-Server sie findet und damit sie von den UI-Komponenten deiner App getrennt bleiben. Jedes Template ist eine schlichte React-Komponente, gebaut aus @react-email/components-Primitives, die unter der Haube auf E-Mail-sicheres HTML abbilden.

// emails/welcome-email.tsx
import {
  Body,
  Button,
  Container,
  Head,
  Heading,
  Html,
  Preview,
  Section,
  Text,
} from "@react-email/components";

interface WelcomeEmailProps {
  name: string;
  dashboardUrl: string;
}

export default function WelcomeEmail({ name, dashboardUrl }: WelcomeEmailProps) {
  return (
    <Html>
      <Head />
      <Preview>Your account is ready</Preview>
      <Body style={{ backgroundColor: "#f6f6f6", fontFamily: "sans-serif" }}>
        <Container style={{ backgroundColor: "#ffffff", padding: "32px", borderRadius: "8px" }}>
          <Heading style={{ fontSize: "20px" }}>Welcome, {name}</Heading>
          <Text style={{ color: "#444", fontSize: "15px", lineHeight: "22px" }}>
            Your account is set up and ready to go. Click below to get started.
          </Text>
          <Section style={{ marginTop: "24px" }}>
            <Button
              href={dashboardUrl}
              style={{
                backgroundColor: "#111",
                color: "#fff",
                padding: "12px 20px",
                borderRadius: "6px",
                fontSize: "14px",
              }}
            >
              Go to dashboard
            </Button>
          </Section>
        </Container>
      </Body>
    </Html>
  );
}

Inline-Styles sind hier Absicht, keine Abkürzung. Die meisten Mail-Clients strippen <style>-Blöcke oder klassenbasiertes CSS, also rendert @react-email/components deine Styles automatisch inline ins finale HTML. Du schreibst normal aussehendes JSX, und es kommt E-Mail-sicher heraus.

Ein lib/email-Helper

Jede Route, die Mail versendet, braucht denselben Resend-Client, also gehört er in einen geteilten Helper, statt überall neu instanziiert zu werden. Das gibt dir auch einen Ort, um später Logging oder einen Fallback zu ergänzen.

// lib/email.ts
import { Resend } from "resend";
import type { ReactElement } from "react";

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

interface SendEmailParams {
  to: string;
  subject: string;
  react: ReactElement;
}

export async function sendEmail({ to, subject, react }: SendEmailParams) {
  const { data, error } = await resend.emails.send({
    from: process.env.EMAIL_FROM!,
    to,
    subject,
    react,
  });

  if (error) {
    console.error("Email send failed:", error);
    return { success: false as const, error };
  }

  return { success: true as const, id: data?.id };
}

Das Resend-SDK wirft bei einem fehlgeschlagenen Send keine Exception, es gibt ein error-Feld auf dem Response-Objekt zurück. Wenn du diesen Check überspringst, sieht eine gebouncte oder abgelehnte E-Mail in deinen Logs genau wie eine erfolgreiche aus. Der Helper oben zwingt jeden Aufrufer, beide Fälle explizit zu behandeln.

Aus einer Server Action senden (Willkommens-E-Mail nach dem Signup)

Die Willkommens-E-Mail sollte direkt nach der Account-Erstellung feuern, ohne den Nutzer auf die Response des E-Mail-Providers warten zu lassen, bevor er in der App landet. Pack den Send in die Server Action, die nach dem abgeschlossenen Signup läuft.

// app/(auth)/signup/actions.ts
"use server";

import { createUser } from "@/lib/db";
import { sendEmail } from "@/lib/email";
import WelcomeEmail from "@/emails/welcome-email";
import { redirect } from "next/navigation";

export async function signUp(formData: FormData) {
  const email = formData.get("email") as string;
  const name = formData.get("name") as string;
  const password = formData.get("password") as string;

  const user = await createUser({ email, name, password });

  const result = await sendEmail({
    to: user.email,
    subject: "Welcome aboard",
    react: WelcomeEmail({
      name: user.name,
      dashboardUrl: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard`,
    }),
  });

  if (!result.success) {
    console.error(`Welcome email failed for user ${user.id}`, result.error);
  }

  redirect("/dashboard");
}

Eine fehlgeschlagene Willkommens-E-Mail sollte niemals die Account-Erstellung blockieren. Der Nutzer hat an dem Punkt schon einen Account, also loggt der Code den Fehler und macht mit dem Dashboard weiter, statt eine Exception zu werfen. Diese Log-Zeile zählt, lass Claude Code sie in deinen bestehenden Error-Tracker einbinden, falls du einen hast, damit ein Anstieg an E-Mail-Fehlern tatsächlich bemerkt wird, statt still in den Server-Logs zu liegen.

Aus einem Route Handler senden (Passwort-Reset)

Ein Passwort-Reset braucht ein Token, das vor dem Send generiert wird, und wird oft von einem Formular ausgelöst, das eine JSON-Response statt eines Redirects erwartet, also passt ein Route Handler hier besser als eine Server Action.

// app/api/auth/reset-password/route.ts
import { NextRequest, NextResponse } from "next/server";
import { createResetToken, getUserByEmail } from "@/lib/db";
import { sendEmail } from "@/lib/email";
import ResetPasswordEmail from "@/emails/reset-password-email";

export async function POST(request: NextRequest) {
  const { email } = await request.json();
  const user = await getUserByEmail(email);

  // Always return success, even if the user doesn't exist.
  // This avoids leaking which emails have accounts.
  if (!user) {
    return NextResponse.json({ success: true });
  }

  const token = await createResetToken(user.id);
  const resetUrl = `${process.env.NEXT_PUBLIC_APP_URL}/reset-password/${token}`;

  const result = await sendEmail({
    to: user.email,
    subject: "Reset your password",
    react: ResetPasswordEmail({ resetUrl }),
  });

  if (!result.success) {
    return NextResponse.json({ success: false, error: "Failed to send email" }, { status: 500 });
  }

  return NextResponse.json({ success: true });
}

Dieselbe Erfolgs-Response zurückzugeben, egal ob der Account existiert oder nicht, ist ein kleines, aber echtes Sicherheitsdetail. Frag Claude Code explizit nach diesem Pattern ("don't leak account existence through the response"), weil eine naive Implementierung einen 404 für unbekannte E-Mails zurückgibt, was genau die Information ist, die du nicht herausgeben willst.

Die Beleg-E-Mail bauen

Belege folgen demselben Pattern wie die Willkommens-E-Mail, nur mit anderem Inhalt und, meistens, einem Stripe-Webhook als Auslöser statt eines Formular-Submits.

// emails/receipt-email.tsx
import { Body, Container, Head, Heading, Hr, Html, Row, Column, Section, Text } from "@react-email/components";

interface ReceiptEmailProps {
  amount: string;
  planName: string;
  invoiceDate: string;
}

export default function ReceiptEmail({ amount, planName, invoiceDate }: ReceiptEmailProps) {
  return (
    <Html>
      <Head />
      <Body style={{ backgroundColor: "#f6f6f6", fontFamily: "sans-serif" }}>
        <Container style={{ backgroundColor: "#ffffff", padding: "32px", borderRadius: "8px" }}>
          <Heading style={{ fontSize: "20px" }}>Payment received</Heading>
          <Section style={{ marginTop: "16px" }}>
            <Row>
              <Column><Text>Plan</Text></Column>
              <Column><Text>{planName}</Text></Column>
            </Row>
            <Row>
              <Column><Text>Amount</Text></Column>
              <Column><Text>{amount}</Text></Column>
            </Row>
            <Row>
              <Column><Text>Date</Text></Column>
              <Column><Text>{invoiceDate}</Text></Column>
            </Row>
          </Section>
          <Hr />
          <Text style={{ color: "#888", fontSize: "12px" }}>
            Questions about this charge? Reply to this email.
          </Text>
        </Container>
      </Body>
    </Html>
  );
}

Ruf das aus deinem Stripe-Webhook-Handler auf, nach einem checkout.session.completed- oder invoice.paid-Event, mit demselben sendEmail-Helper von vorhin. Stripe versucht fehlgeschlagene Webhooks erneut, also wenn dein E-Mail-Send innerhalb dieses Handlers eine Exception wirft, wrappe ihn, damit ein schlechter Send Stripe nicht denken lässt, der ganze Webhook sei fehlgeschlagen, und das gesamte Event erneut auslöst.

Templates lokal previewen

Du willst nicht bei jeder Anpassung einer Schriftgröße eine echte E-Mail verschicken. Die React-Email-CLI startet einen lokalen Server, der deine Templates live rendert und beim Speichern neu lädt, ohne API-Key oder Netzwerk-Call.

npx react-email dev

Das öffnet einen Preview auf localhost:3000 (oder dem nächsten freien Port), der jede Komponente in deinem emails/-Ordner zeigt, gerendert, wie sie in einem Postfach erscheinen würde, mit einem Umschalter, um das rohe HTML-Output zu inspizieren. Zeig mit --dir auf einen eigenen Ordner, falls deine Templates nicht am Standardort liegen. Lass Claude Code Mock-Props für jedes Template übergeben, damit der Preview echt aussehenden Inhalt statt leerer Felder zeigt.

Fehler und Idempotenz behandeln

Zwei Fehlermodi zählen in Produktion. Erstens kann ein langsamer oder fehlgeschlagener Call an Resend innerhalb einer Server Action einen Request hängen lassen, wenn du keine vernünftige Erwartung setzt, wie lange du wartest. Zweitens können Retries (ein Nutzer, der doppelt auf Submit klickt, ein Webhook, der zweimal feuert) Doppel-Sends auslösen, wenn nichts sie stoppt.

Zum Doppel-Schutz übergib einen Idempotency-Key bei Sends, die von externen Events wie Webhooks ausgelöst werden, damit ein wiederholter Webhook nicht denselben Beleg zweimal schickt.

await resend.emails.send(
  {
    from: process.env.EMAIL_FROM!,
    to: user.email,
    subject: "Payment received",
    react: ReceiptEmail({ amount, planName, invoiceDate }),
  },
  { idempotencyKey: `receipt-${invoiceId}` }
);

Die Stripe-Invoice-ID als Idempotency-Key zu nutzen bedeutet, dass Resend einen wiederholten Request mit demselben Key erkennt und das erneute Senden überspringt, statt einen zweiten Beleg zu feuern. Das zählt am meisten bei webhook-ausgelösten Sends, wo Retries erwartetes Verhalten sind, kein Edge Case.

Testen, bevor du auslieferst

Bevor du das in Signup oder Checkout einbaust, schick eine echte Test-E-Mail an dein eigenes Postfach und prüf drei Dinge von Hand: dass die Betreffzeile nicht abgeschnitten ist, dass der Button tatsächlich auf die richtige URL verlinkt und dass die E-Mail nicht im Spam landet. Gmail und Outlook behandeln Inline-Styles unterschiedlich, also prüf beide, wenn du kannst.

claude "send a test welcome email to my inbox using the sendEmail helper and confirm the response includes a data.id"

Sobald das klappt, sieh im Logs-Tab des Resend-Dashboards nach. Jeder Send zeigt seinen Zustellstatus (delivered, bounced, complained), was der schnellste Weg ist, um zu bestätigen, dass deine Domain-Verifizierung tatsächlich von Anfang bis Ende funktioniert, nicht nur von der API akzeptiert wurde.

E-Mail von Hand zu verkabeln, ein Endpoint nach dem anderen, ist die Art Aufgabe, die Claude Code in einer einzigen Session gut bewältigt. Der schwierigere Teil ist alles drumherum: die Domain korrekt verifizieren, sicherstellen, dass ein fehlgeschlagener Send nicht still den Signup kaputtmacht, und die Templates konsistent halten, während du mehr davon hinzufügst. Das $29 Code Kit liefert das schon verkabelt in das Framework, das es aufsetzt, Willkommens-, Beleg- und Reset-Templates inklusive, damit du von einem funktionierenden E-Mail-System startest, statt eins von Grund auf zu bauen.

Posted by @speedy_devv

Continue in Workflow

  • Agentic Commerce: Wie du eine App baust, für die KI-Agents bezahlen können
    Ein Guide in einfachem Deutsch zu Agentic Commerce im Jahr 2026: Was x402, ACP und das Machine Payments Protocol tun, plus eine Wochenend-Anleitung, um eine bezahlte API auszuliefern, von der KI-Agents kaufen können.
  • Claude Code Best Practices
    Fünf Gewohnheiten trennen Entwickler, die mit Claude Code liefern: PRDs, modulare CLAUDE.md-Regeln, Custom-Slash-Commands, /clear-Resets und eine System-Evolutions-Denkweise.
  • Claude Code Auto-Modus
    Ein zweites Sonnet-Modell prüft jeden Claude Code-Tool-Aufruf, bevor er ausgeführt wird. Was der Auto-Modus blockiert, was er erlaubt, und die Erlaubnisregeln, die er in deine Einstellungen schreibt.
  • Channels, Routines, Teleport, Dispatch
    Die vier Claude-Code-Features, die Anthropic im März und April 2026 ausgeliefert hat und die die CLI in eine ereignisgesteuerte Koordinationsschicht über Handy, Web und Desktop verwandeln.
  • Claude Code 1M Context in Practice: When Bigger Isn't Better
    The 1M-token context window is GA at flat pricing, but bigger isn't always better. A decision framework, token-cost math, and when to use /compact, subagents, and dynamic workflows instead.
  • How to Build an Admin Dashboard With Claude Code
    Ship an internal admin panel with Claude Code: role-gated routes, a searchable users and orders table with pagination, impersonation-safe RLS, and metrics tiles pulled through a type-safe API.

More from Handbook

  • Best SaaS Boilerplate 2026: The Honest Comparison
    An honest 2026 roundup of the best SaaS boilerplates and starter kits (ShipFast, Makerkit, Supastarter, SaaS Pegasus, Divjoy, open source), with real pricing, stacks, and the trade-off nobody mentions: a boilerplate still leaves you coding.
  • Claude Code Changelog
    Release-Notizen für Claude Code von der v0.2-Beta bis März 2026. Bare-Modus, Channels-Berechtigungsweiterleitung, OAuth-Fixes und alle Breaking Changes.
  • What It Really Costs to Build a SaaS MVP in 2026
    A buyer's cost breakdown for building a SaaS MVP in 2026. Real freelancer, agency, no-code, AI-tool, and DIY numbers with citations, plus where the money actually goes.
  • Prompt-Vorlagen, die Code liefern
    Zehn Prompt-Rezepte, die Code liefern: Full-Stack-Scaffolding, APIs, Schemas, Tests, Refactoring, Debugging, Reviews und CI. Jeweils mit den Fehlermodi, die es zu vermeiden gilt.

Sie möchten das Framework hinter diesen Projekten?

Holen Sie sich das Claude Code System, mit dem wir produktionsreife Software planen, bauen, testen und ausliefern.

Sehen Sie, was wir für Unternehmen bauen →
speedy_devvkoen_salo

Authentifizierung mit Claude Code einbauen (Supabase Auth)

Ergänze E-Mail/Passwort-Signup, Google OAuth, Magic Links, geschützte Routen und Session-Handling in einer Next.js-16-App mit Claude Code und Supabase Auth.

Eine typsichere API mit Claude Code bauen (oRPC + Zod)

So baust du eine vollständig typsichere API-Schicht in Next.js 16 mit oRPC und Zod, mit Claude Code, sodass Schema-Änderungen den Build brechen statt der Produktion.

On this page

Warum Transaktions-E-Mail knifflig ist
Resend und React Email installieren
Einen API-Key holen und deine Absender-Domain verifizieren
Ein React-Email-Template bauen
Ein lib/email-Helper
Aus einer Server Action senden (Willkommens-E-Mail nach dem Signup)
Aus einem Route Handler senden (Passwort-Reset)
Die Beleg-E-Mail bauen
Templates lokal previewen
Fehler und Idempotenz behandeln
Testen, bevor du auslieferst

Sie möchten das Framework hinter diesen Projekten?

Holen Sie sich das Claude Code System, mit dem wir produktionsreife Software planen, bauen, testen und ausliefern.

Sehen Sie, was wir für Unternehmen bauen →