nextauthjs/next-auth · error · Error
Loops Send Error: ${JSON.stringify(await res.json())}
Error message
Loops Send Error: ${JSON.stringify(await res.json())} What it means
Thrown when the POST to https://app.loops.so/api/v1/transactional returns a non-OK status after credentials were present. The provider embeds the API's JSON error response in the message so you can see Loops' reason (invalid key, unknown transactionalId, missing variable, rate limit). The verification email is not sent, blocking sign-in.
Source
Thrown at packages/core/src/providers/loops.ts:74
if (!provider.apiKey || !provider.transactionalId)
throw new TypeError("Missing Loops API Key or TransactionalId")
const res = await fetch("https://app.loops.so/api/v1/transactional", {
method: "POST",
headers: {
Authorization: `Bearer ${provider.apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
transactionalId: provider.transactionalId,
email: to,
dataVariables: {
url: url,
},
}),
})
if (!res.ok) {
throw new Error("Loops Send Error: " + JSON.stringify(await res.json()))
}
},
options: config,
}
}
View on GitHub (pinned to a1a16a5a77)
Solutions
- Read the JSON error body in the message — it names the exact Loops-side problem
- Verify the API key belongs to the same workspace as the transactionalId and is still active
- Confirm the transactionalId matches an existing published transactional email containing the {{url}} variable
- Retry with backoff if the response indicates rate limiting (429) or a 5xx
Example fix
// before
// transactionalId from an old deleted template -> Loops returns 404
transactionalId: "clx_old_id"
// after
// copy the current ID from app.loops.so transactional email page
transactionalId: process.env.LOOPS_TRANSACTIONAL_ID,
dataVariables: { url }, // must match template variables exactly Defensive patterns
Strategy: try-catch
Validate before calling
if (!process.env.LOOPS_API_KEY) throw new Error('LOOPS_API_KEY is required')
if (!process.env.LOOPS_TRANSACTIONAL_ID) throw new Error('LOOPS_TRANSACTIONAL_ID is required') // must exist in your Loops workspace Type guard
function isLoopsConfig(c: { apiKey?: string; transactionalId?: string }): c is { apiKey: string; transactionalId: string } {
return !!c.apiKey && !!c.transactionalId
} Try / catch
try {
await sendVerificationRequest(params)
} catch (e) {
const msg = e instanceof Error ? e.message : String(e)
console.error('Loops send failed:', msg) // contains the API JSON error
const status = /429/.test(msg) ? 429 : undefined
if (status === 429) scheduleRetryWithBackoff(params)
} Prevention
- Read the JSON body embedded in the error message — it names the exact Loops-side cause
- Keep the transactional template and its dataVariables (url) in sync with code
- Use an API key from the same workspace as the transactionalId
- Add retry/backoff for 429 and 5xx responses
When it happens
Trigger: Loops transactional API rejects the request during sendVerificationRequest: expired/revoked API key, transactionalId that doesn't exist in the account, payload rejected (e.g. missing required dataVariables), or HTTP 429/5xx. Any res.ok === false triggers the throw.
Common situations: Using an API key from a different Loops workspace; deleting or renaming the transactional email template after deploy; malformed dataVariables not matching the template; hitting Loops rate limits during traffic spikes.
Related errors
- Mailgun error: + (await res.text())
- Forward Email error: ${JSON.stringify(await res.json())}
- Missing Loops API Key or TransactionalId
- Another account already exists with the same e-mail address
- Verification
AI-assisted analysis of nextauthjs/next-auth@a1a16a5a77 (2026-08-28).
Data as JSON: /api/errors/ac55646508f914a1.
Report an issue: GitHub.