Auth.js
Send Auth.js and NextAuth magic-link email through Samva with a custom HTTP email provider.
Auth.js
Auth.js email providers send magic links through one
callback: sendVerificationRequest. Use a custom HTTP email provider and call
samva.messages.send from that callback.
Auth.js owns authentication and verification-token storage. Samva sends the
email over your verified sender, so the Samva payload has no from field.
Install
bun add next-auth@beta samvaKeep SAMVA_API_KEY server-side only. Auth.js also needs AUTH_SECRET; generate
it with:
npx auth secretAdd the provider
import NextAuth from "next-auth";
import type { EmailConfig } from "next-auth/providers/email";
import { createClient } from "samva";
const apiKey = process.env.SAMVA_API_KEY;
if (!apiKey) {
throw new Error("SAMVA_API_KEY is not set.");
}
const samva = createClient({ apiKey });
function SamvaEmail(config: Partial<EmailConfig> = {}): EmailConfig {
return {
id: "samva",
type: "email",
name: "Email",
from: "",
maxAge: 24 * 60 * 60,
async sendVerificationRequest({ identifier: to, url }) {
const { host } = new URL(url);
await samva.messages.send({
to: [{ email: to }],
channel: "email",
email: {
subject: `Sign in to ${host}`,
html: `<p><a href="${url}">Sign in to ${host}</a></p>`,
text: `Sign in to ${host}\n${url}\n`,
},
});
},
options: config,
};
}
export const { handlers, signIn, signOut, auth } = NextAuth({
// adapter: <your database adapter>,
providers: [SamvaEmail()],
});The empty provider-level from exists only because Auth.js' email-provider type
still carries that field. It is not forwarded to Samva.
Add the route handler
import { handlers } from "@/auth";
export const { GET, POST } = handlers;Place that in app/api/auth/[...nextauth]/route.ts.
Trigger sign-in
import { signIn } from "@/auth";
export function SignInForm() {
return (
<form
action={async (formData) => {
"use server";
await signIn("samva", formData);
}}
>
<input type="email" name="email" required />
<button type="submit">Email me a sign-in link</button>
</form>
);
}Template with React Email
Render a React Email component to html, derive text, then pass both strings
to Samva. Replace sendVerificationRequest inside the SamvaEmail provider
factory with:
import { render, toPlainText } from "react-email";
import { MagicLinkEmail } from "./emails/magic-link";
async sendVerificationRequest({ identifier: to, url }) {
const { host } = new URL(url);
const html = await render(<MagicLinkEmail host={host} url={url} />);
const text = toPlainText(html);
await samva.messages.send({
to: [{ email: to }],
channel: "email",
email: { subject: `Sign in to ${host}`, html, text },
});
}See the React Email integration for the full rendering and preview workflow.
Runtime notes
The Samva send is edge-safe because the SDK is fetch-based. The database
adapter you choose for Auth.js verification-token storage is the runtime gate:
many adapters need Node, or Auth.js' split-config pattern with auth.config.ts
for edge proxy/middleware and full auth.ts with the adapter elsewhere.
NextAuth v4's EmailProvider uses the same sendVerificationRequest seam, but
the wiring differs; see the
Auth.js v5 migration guide.
For delivery and bounce events, see Verify webhooks.
Cookbook
The cookbook link resolves after the companion samva-integrations PR lands.