Features · Sign-in & email
Google sign-in, minus Google Cloud.
“Continue with Google” and email sign-in for the people who use your app — no Google Cloud project, no OAuth client, no keys. Its sign-up mail goes out in your app’s name, from your own domain once it verifies.
What your agent calls
appAuthConfigureappAuthStatusappAuthEmailConnectappAuthEmailStatus
Let staff sign in with Google
Update(app/login/page.tsx)
1 line · Google button → oauth.trytofu.ai/start
Write(app/auth/confirm/route.ts)
new route · verifyOtp for the email link
tofu · appAuthConfigure confirmed: true
configured · Google sign-in, and email sign-in as well
How Google sign-in works
Tofu talks to Google, so your app does not have to.
Tofu’s broker at oauth.trytofu.ai owns one Google client and one permanent callback. Your app sends the browser there; the broker signs the person into your app’s own auth service — the Supabase project behind its managed database.
- Your app“Continue with Google” sends the browser to Tofu’s broker.
- oauth.trytofu.aiChecks the return address is one of your app’s own, and nothing else.
- GoogleThe person signs in, on the consent screen of Tofu’s client.
- Your app’s auth serviceThe account is created or found there, and the browser comes back signed in.
Because the broker is the one party that talks to Google, there is no Google Cloud project, OAuth client or key on your side — and no per-app console step for anyone.
appAuthConfigure writes Google and email sign-in into your app’s auth service in one confirmed step, then reads the configuration back before it says configured.
Deploying with a database does not turn sign-in on by itself: it is one explicit call after the deploy, and safe to repeat.
// Continue with Google — Tofu brokers the sign-in.
window.location.href = "https://oauth.trytofu.ai/start?return=" +
encodeURIComponent(window.location.href);&flow=pkce and redeems the sign-in in a small route of its own. Your agent writes both.Sign-up email, in your app’s name.
Email sign-in is switched on in the same step, so an account always has a way back in if a third-party sign-in is ever unavailable.
Your app’s own auth service sends its confirmation, magic-link, password-reset, invitation, address-change and reauthentication mail through Tofu’s email setup, with your app’s name as the sender. No SMTP password, and no email provider to sign up for.
Every message carries a one-time code as well as a link, because mail scanners that open links first would otherwise spend the token before your user could.
| When | Sent from |
|---|---|
| Until your domain verifies | noreply@notify.trytofu.app |
| Once it verifies | noreply@send.frankskitchen.com |
From your domain
Six records, none at your apex.
Once your domain is connected, appAuthEmailConnect creates a sending identity for send.frankskitchen.com and publishes its records — all under send., so the mail your domain already receives is untouched. Remove them, and the domain is exactly as it was.
| Type | Name, in frankskitchen.com | Value |
|---|---|---|
| CNAME | ‹token 1›._domainkey.send | ‹token 1›.dkim.amazonses.com |
| CNAME | ‹token 2›._domainkey.send | ‹token 2›.dkim.amazonses.com |
| CNAME | ‹token 3›._domainkey.send | ‹token 3›.dkim.amazonses.com |
| MX 10 | bounce.send | feedback-smtp.‹region›.amazonses.com |
| TXT | bounce.send | v=spf1 include:amazonses.com ~all |
| TXT | _dmarc.send | v=DMARC1; p=none; rua=mailto:… |
With the Cloudflare approval your domain connection already holds, Tofu writes them itself — no second approval. Anywhere else, it lists them to add, and asks the email provider to check.
Until the email provider reports the domain verified, mail keeps going out from notify.trytofu.app — sign-up never stops while you wait.
A link a scanner cannot spend.
Each message links to /auth/confirm on your app’s own address, and the token is spent only when your app calls verifyOtp — so a scanner that opens the link first reaches your app, not the sign-in service.
A new app built through the /tofu skill gets this route; your agent writes it. An app deployed without it still signs people in with the code in each message.
import { createClient } from '@/lib/supabase/server';
import { NextResponse } from 'next/server';
export async function GET(request) {
const { searchParams, origin } = new URL(request.url);
const token_hash = searchParams.get('token_hash');
const type = searchParams.get('type');
if (token_hash && type) {
const supabase = await createClient();
const { error } =
await supabase.auth.verifyOtp({ type, token_hash });
if (!error) return NextResponse.redirect(`${origin}/`);
}
return NextResponse.redirect(`${origin}/login?error=link`);
}Limits
What is not here yet
What sign-in and its mail on Tofu do not do.
The consent screen shows Tofu’s name. Google shows the verified name of the client it signs in through, so people read “continue to Tofu”. Your own name there needs a Google client of your own, which is not built.
Only with a Tofu-managed database. Sign-in is written into the auth service behind your app’s managed database. An app on Clerk, Auth0 or a Supabase project of its own is left exactly as it is.
Google and email, nothing else. No GitHub or Apple sign-in for your app’s users.
Sign-in mail, not your app’s other mail. Tofu sends the sign-in and sign-up messages; receipts, notifications and anything else your app sends do not go through Tofu.
Your own sender needs your own domain. The domain has to be connected to the app first; until it verifies, mail comes from noreply@notify.trytofu.app.
The own-domain sender is the newest part. Its records, verification and sender switch have run end to end on Tofu’s own test project, not yet on a customer’s app.
Paste one line. Go live.
Your app, its database, your domain and your sign-in — from the agent you already use, in about 10 minutes.
The ship-it layer for vibe-coded apps. Your agent wrote it — Tofu ships it.
Works in all coding agents
© 2026 Tofu
trytofu.ai