Why block disposable emails at signup
A disposable address lets someone create an account they never intend to return to. The cost shows up later: free-trial credits claimed again and again, referral rewards farmed with throwaway inboxes, AI or API allowances drained by fresh accounts, and verification emails that bounce after the inbox expires. Checking the email domain once, when the account is created, is the cheapest point to stop that. It is also the point where a polite message such as "please use a permanent address" costs a real user the least.
Where in the flow to check
Run the check on your server, inside the request that creates the account, before you send a verification email or grant any credit. A browser-only check is easy to skip, and an API key in front-end code can be copied. Keep the check close to the insert: if two signups race, the second one should still be evaluated before it receives a reward. Disban only needs the domain; sending the full address is fine, but only the domain is used for classification.
Call the API from your server
Send the address to POST /api/check with your API key in the Authorization header. The response includes a category, a reason, the evidence that matched, and a separate mail_routing status. The same call works for a single address; POST /api/batch accepts up to 50 entries when you are reviewing an existing user list.
curl -X POST 'https://disban.io/api/check' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{"input":"[email protected]"}'Decide what each category means for your product
Act on the category, not on a single true/false flag. disposable: ask for a permanent address, or allow the account but withhold credits and rewards. free (Gmail, Outlook and other consumer mail): allow; most real people use these. alias (privacy relays such as SimpleLogin or Firefox Relay): allow; an alias forwards to a real inbox and is a privacy choice, not abuse. testing (inboxes built for QA): reject in production signup flows. parked: the domain is parked and is unlikely to receive mail, so ask the user to check the address. unknown: allow and flag for later review; unknown means there was not enough evidence, never that the address is safe.
Check that the domain can receive mail
Read mail_routing separately from the category. invalid means the domain has no current public mail route, which is usually a typo such as gmial.com, so ask the user to correct it. routable means a route exists; it does not prove that the mailbox exists or that the person owns it. Keep your normal email verification step for that.
Node.js (Express or any server)
A small helper that returns a decision you can store with the account. It treats a lookup failure as "allow but hold rewards" instead of rejecting the user.
async function checkSignupEmail(email) {
const res = await fetch("https://disban.io/api/check", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.DISBAN_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ input: email }),
signal: AbortSignal.timeout(4000),
});
if (!res.ok) return { allow: true, holdRewards: true }; // 429 / 503: retry later
const r = await res.json();
if (r.mail_routing?.status === "invalid") return { allow: false, message: "Check your email address." };
if (r.category === "disposable" || r.category === "testing")
return { allow: false, message: "Please use a permanent email address." };
return { allow: true, holdRewards: r.category === "unknown" };
}Next.js route handler
Call the helper from the route that creates the account, never from client components, so the key stays on the server.
// app/api/signup/route.ts
export async function POST(req: Request) {
const { email, name } = await req.json();
const decision = await checkSignupEmail(email);
if (!decision.allow)
return Response.json({ error: decision.message }, { status: 422 });
const user = await createUser({ email, name, rewardsOnHold: decision.holdRewards });
await sendVerificationEmail(user);
return Response.json({ ok: true });
}Python (Django view)
The same decision table in Python with requests. Keep the timeout short so a slow lookup never stalls signup.
import os, requests
from django.http import JsonResponse
def check_signup_email(email):
try:
r = requests.post("https://disban.io/api/check",
headers={"Authorization": f"Bearer {os.environ['DISBAN_API_KEY']}"},
json={"input": email}, timeout=4)
r.raise_for_status()
except requests.RequestException:
return {"allow": True, "hold_rewards": True}
data = r.json()
if data.get("mail_routing", {}).get("status") == "invalid":
return {"allow": False, "message": "Check your email address."}
if data["category"] in ("disposable", "testing"):
return {"allow": False, "message": "Please use a permanent email address."}
return {"allow": True, "hold_rewards": data["category"] == "unknown"}Go
A minimal client using the standard library.
type checkResult struct {
Category string `json:"category"`
MailRouting struct {
Status string `json:"status"`
} `json:"mail_routing"`
}
func checkSignupEmail(ctx context.Context, email string) (allow, holdRewards bool) {
body, _ := json.Marshal(map[string]string{"input": email})
req, _ := http.NewRequestWithContext(ctx, "POST", "https://disban.io/api/check", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("DISBAN_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := (&http.Client{Timeout: 4 * time.Second}).Do(req)
if err != nil || res.StatusCode != http.StatusOK {
return true, true
}
defer res.Body.Close()
var r checkResult
if json.NewDecoder(res.Body).Decode(&r) != nil {
return true, true
}
if r.MailRouting.Status == "invalid" || r.Category == "disposable" || r.Category == "testing" {
return false, false
}
return true, r.Category == "unknown"
}Handle outages and limits without blocking real users
Two responses need a plan. 503 with code verification_unavailable means mail routing could not be verified in time; no credit is charged and the response carries Retry-After. 429 means you reached your plan or per-second limit. In both cases, do not reject the user and do not treat the address as clean. Create the account, hold free credits or referral rewards, and re-check in the background before releasing them.
Write a message people can act on
Tell people what to do, not what you suspect: "Please use a permanent email address. Temporary inboxes can't receive account notices." Avoid accusing language, and give a route to support for mistakes. Disban accepts domain corrections with evidence, and a reviewed correction applies to new checks.
Measure the rule after launch
Log the category and decision for each signup, without storing more of the address than you need. Compare completion rates, support requests and later activity by category. If unknown or alias users behave like everyone else, keep allowing them. If a pattern of abuse appears, tighten the rule for rewards first, not for access.
Source for provider behavior: Disban API documentation. Product-policy guidance is Disban's interpretation.