Magic Link Email Limit Exceeded
magic_link_email_limit_exceeded429
Too many magic link requests for this email address. Wait 1 hour before requesting another.
What this means
A specific email address has requested too many magic links in a short window. The retry_after field is 3600 (one hour) by contract. This limit exists to prevent email-bombing of individual inboxes — even from different IPs, even from rotating clients, one email address can only receive a limited number of magic links per hour.
When you'll see this
- A user clicked "send magic link" many times rapidly when the email felt slow.
- An attacker is targeting one user's inbox with sign-in spam.
- A QA process is automating magic-link requests against a fixed test email.
- The user has multiple devices and each one tried to send a magic link independently.
Learn more about how this works
Where magic_link_ip_limit_exceeded protects against one client flooding many emails, this limit protects one email address from being flooded by many clients. Together they make magic-link spam impractical — an attacker can't rotate IPs to bypass per-IP limits, and they can't rotate target emails to bypass per-email limits.
The most common gotcha: this limit is per email, not per user account. If a user has multiple browser tabs open, each one might queue up a magic-link request independently, and they'll all hit this limit together. The fix is debouncing in your UI, not raising the limit.
Example response
{
"success": false,
"error": "magic_link_email_limit_exceeded",
"message": "Too many magic link requests for this email.",
"details": [],
"retry_after": 3600,
"doc_url": "https://docs.asterwise.com/reference/errors/magic_link_email_limit_exceeded",
"request_id": "req_01HXYZABCDEFGH",
"timestamp": "2026-05-25T12:34:56Z"
}
- Wait 60 minutes. The per-email limit will clear automatically.
- If the user can still access their account through another mechanism (existing session, password if applicable), use that path instead while the limit clears.
- If a QA flow keeps tripping this, switch to a rotating pool of test emails (e.g.
test+{uuid}@yourdomain.com).
This and magic_link_ip_limit_exceeded should usually share a single handler — both are 429s with a 1-hour cooldown, and both need the same UX treatment.
Python:
Production handler
- Python
- TypeScript
import httpx
MAGIC_LINK_LIMITS = {
"magic_link_ip_limit_exceeded",
"magic_link_email_limit_exceeded",
}
def request_magic_link(email, base_url, headers):
response = httpx.post(
f"{base_url}/v1/auth/magic-link",
headers=headers,
json={"email": email},
timeout=10,
)
if response.status_code == 429:
body = response.json()
if body.get("error") in MAGIC_LINK_LIMITS:
wait_minutes = (body.get("retry_after", 3600)) // 60
return {
"ok": False,
"user_message": (
f"Please wait {wait_minutes} minutes before "
"requesting another sign-in email."
),
}
response.raise_for_status()
return {"ok": True}
const MAGIC_LINK_LIMITS = new Set([
"magic_link_ip_limit_exceeded",
"magic_link_email_limit_exceeded",
]);
async function requestMagicLink(email: string, baseUrl: string, headers: HeadersInit) {
const response = await fetch(`${baseUrl}/v1/auth/magic-link`, {
method: "POST",
headers,
body: JSON.stringify({ email }),
});
if (response.status === 429) {
const body = await response.json();
if (MAGIC_LINK_LIMITS.has(body.error)) {
const waitMinutes = Math.floor((body.retry_after ?? 3600) / 60);
return {
ok: false,
userMessage:
`Please wait ${waitMinutes} minutes before ` +
`requesting another sign-in email.`,
};
}
}
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return { ok: true };
}
Avoid this error by
- Debounce the "send magic link" button for 60 seconds after a successful request. This single change eliminates most legitimate hits on this error.
- Show users the most recent send timestamp in the UI: "Magic link sent 30 seconds ago. Check your inbox." This stops the "did it work?" reflex click.
- In test environments, use rotating email addresses (
test+{timestamp}@yourdomain.com) so each test gets a fresh per-email counter. - For high-trust users (recently active sessions, etc.), consider a session refresh flow that doesn't require a new magic link at all.