Use case

Add auth and transactional email to Next.js

Install the AscendKit JS SDK, set your public key, and you get hosted auth (credentials, magic link, OAuth) plus transactional email from the same project. One user record covers both, so a signup can trigger a welcome email without wiring a webhook between two vendors.

How it works

  1. 1. Install the SDK
    Add @ascendkit/nextjs and set your project public key via the X-AscendKit-Public-Key header. The CLI or MCP server can scaffold this for you.
  2. 2. Mount auth
    Use the server and client auth hooks for credentials, magic link, and OAuth (Google, GitHub, LinkedIn). Users are created as usr_ records automatically.
  3. 3. Send email
    Author a versioned email template in the portal, then send it from the SDK. AWS SES delivery, DKIM/SPF, and a verified domain are handled for you.

What does the full Next.js setup look like?

Three files and two environment variables. Mount the auth runtime, expose the catch-all auth route, wrap the app in the provider — and transactional email is already available from the same project, because it is the same platform rather than a second integration.

The environment variables come in pairs, one server-side and one exposed to the browser. Running the CLI writes them for you; setting them by hand works equally well. ASCENDKIT_ENV_KEY is your project public key and ASCENDKIT_API_URL points at the backend, each with a NEXT_PUBLIC_ twin for client-side use.

What you do not build is the part that usually takes the week: a webhook endpoint receiving user.created from an auth vendor, a retry queue for when it fails, an idempotency check so retries do not double-send, and a reconciliation job for the records that drift anyway.

app/api/auth/[...all]/route.ts
import {
  createAscendKitAuthRuntime,
  createAuthRouteHandlers,
} from "@ascendkit/nextjs/server";

export const authRuntime = createAscendKitAuthRuntime();

export const { GET, POST } = createAuthRouteHandlers(authRuntime);

How do you send a welcome email after signup?

Address the usr_ profile directly. There is no list to sync, no contact to create in an email vendor, and no webhook to fire — the user already exists in the system that sends the email.

Templates are authored and versioned in the dashboard rather than hardcoded in your app, so copy changes ship without a deploy. Each template has HTML and plain-text bodies and supports variable placeholders that resolve against the user record.

The comparison worth making is not feature-by-feature. With Clerk plus Resend, a welcome email requires a webhook endpoint, signature verification, a contact upsert into Resend, and a failure path for when the webhook does not arrive. Here it is a function call against a record that is already there.

What signup-to-welcome-email requires, by stack.
StepAscendKitClerk + Resend
User createdusr_ profileClerk user
Get user into email systemAlready thereWebhook + contact upsert
Verify webhook signatureNot applicableRequired
Handle webhook retriesNot applicableIdempotency key needed
Reconcile drifted recordsNot applicablePeriodic job
Vendors to configure12

Which auth providers can you enable?

Credentials, magic link, and OAuth through Google, GitHub, and LinkedIn. Providers are environment configuration rather than code, so enabling one changes the rendered sign-in form without a deploy.

Two constraints are worth knowing before you design the flow. Magic link and credentials are mutually exclusive — enabling magic link disables password sign-in for that environment, because they are alternative primary factors. OAuth combines with either.

The second is that OAuth works without you registering your own applications. AscendKit's OAuth proxy uses shared credentials, so you can ship Google and GitHub login before you have gone through each provider's app review. You can move to your own credentials later without changing your integration code.

Troubleshooting

Auth works locally but sessions do not persist in production

Cause: The client-side environment variables are missing from the deployment. NEXT_PUBLIC_ASCENDKIT_ENV_KEY and NEXT_PUBLIC_ASCENDKIT_API_URL must be set in the hosting provider, not only in .env.local.

Fix: Set both NEXT_PUBLIC_ variables in your deployment environment and redeploy. Local .env files are not uploaded.

Emails send in the dashboard test but not from the app

Cause: Sends from your application are server-side and require the secret key, which is deliberately never exposed to the browser.

Fix: Set ASCENDKIT_SECRET_KEY server-side and send from a route handler or server action, never from a client component.

Signup succeeds but no welcome email arrives

Cause: Most often an unverified sending domain rather than a code problem.

Fix: Verify your domain in the dashboard so DKIM and SPF are configured, then check the email delivery logs to see whether a send was attempted and what SES returned.

FAQ

Do I need Resend or another email provider alongside auth?

No. Auth and transactional email live in the same AscendKit project, so a verified sign-up can send a welcome email without a second vendor or a webhook bridge between them.

Does this work with the Next.js App Router?

Yes. The SDK ships server and client helpers for the App Router, including the catch-all route handler for the auth flow. Next.js 14 or later and React 18 or later are peer dependencies.

Can I use this with a Python or Go backend?

The server-side auth runtime requires Next.js. Other backends verify AscendKit access tokens instead — the Python SDK covers that, and any language can validate the JWT against the published JWKS endpoint.

Where do the user records live?

On AscendKit, in Better Auth's standard schema. That is what lets auth and email address the same profile without a sync layer between two vendors.

Related guides

Start with one API key

Auth, email, surveys, and journeys share one user record, so you ship this without stitching vendors together.

Start free