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

  1. Read the JSON error body in the message — it names the exact Loops-side problem
  2. Verify the API key belongs to the same workspace as the transactionalId and is still active
  3. Confirm the transactionalId matches an existing published transactional email containing the {{url}} variable
  4. 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

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


AI-assisted analysis of nextauthjs/next-auth@a1a16a5a77 (2026-08-28). Data as JSON: /api/errors/ac55646508f914a1. Report an issue: GitHub.