Skip to content
CyberTemp
All posts
Build a Discord Bot That Hands Out Burner Inboxes

Build a Discord Bot That Hands Out Burner Inboxes

CyberTemp6 min read

Discord servers that run structured account-creation work — closed betas, sneaker-drop coordination, QA communities juggling test accounts — all hit the same wall eventually. Someone opens a browser tab, generates a throwaway inbox, watches it, and pastes the verification code back into chat. That's fine for one person doing it twice a day. It breaks the moment ten people need a fresh inbox in the same five minutes. Wiring CyberTemp's API into a bot turns that manual relay into a slash command that runs itself.

What the bot actually needs to do

Before writing anything Discord-specific, it helps to split the job into three pieces: issue an address, wait for mail, deliver the result somewhere the user will actually see it. Everything else — the command wrapper, the permission checks — is scaffolding around those three steps.

  • A unique address per invocation, so two users running the command in the same minute don't end up reading each other's mail.

  • A polling loop that doesn't hammer the API on a fixed one-second timer.

  • Delivery by DM, not in the channel — a verification code is exactly the kind of thing that shouldn't sit in public chat history.

  • A cleanup step that frees the inbox slot once the code has been delivered.

Wiring the API key into the command

The API takes one header, X-API-Key: tk_<your key>, and creating an inbox is implicit: the first GET /getMail call for an address that doesn't exist yet creates it and returns an empty array. There's no separate provisioning call to make first.

import os
import asyncio
import httpx
import discord
from discord import app_commands

API_KEY = os.environ["CYBERTEMP_API_KEY"] HEADERS = {"X-API-Key": API_KEY} BASE = "https://api.cybertemp.xyz"

intents = discord.Intents.default() client = discord.Client(intents=intents) tree = app_commands.CommandTree(client)

@tree.command(name="burner", description="Get a throwaway inbox") async def burner(interaction: discord.Interaction): address = f"user{interaction.user.id}-{interaction.id}@cybertemp.xyz" async with httpx.AsyncClient() as http: await http.get(f"{BASE}/getMail", headers=HEADERS, params={"email": address}) await interaction.response.send_message( f"Inbox ready: {address}. I'll DM you the code once it lands.", ephemeral=True, ) asyncio.create_task(deliver_when_ready(interaction.user, address))

The address itself doesn't need any special shape — it just needs to be unique enough that concurrent commands don't collide. Baking the Discord user ID and interaction ID into the local part does that without a database lookup on your side.

Polling for the mail without hammering the API

The delivery function is where most first attempts go wrong: they poll on a fixed one-second timer until something breaks. CyberTemp rate-limits by plan, and a fixed-interval poll across dozens of concurrent /burner calls will eventually earn a 429 with a retryAfter field. Respect it instead of retrying blind.

async def deliver_when_ready(user, address, timeout=300):
loop = asyncio.get_event_loop()
deadline = loop.time() + timeout
delay = 3
async with httpx.AsyncClient() as http:
while loop.time() < deadline:
resp = await http.get(
f"{BASE}/getMail", headers=HEADERS, params={"email": address}
)
if resp.status_code == 429:
await asyncio.sleep(resp.json().get("retryAfter", delay))
continue
messages = resp.json()
if messages:
body = (messages[0].get("body") or "")[:500]
await user.send(f"Mail for {address}:\n{body}")
await http.delete(f"{BASE}/inbox", headers=HEADERS, params={"email": address})
return
await asyncio.sleep(delay)
delay = min(delay * 1.5, 20)
await user.send(
f"Nothing arrived for {address} in {timeout}s. "
"Run /burner again closer to when you actually submit the signup form."
)

The backoff — starting at three seconds, creeping up to a twenty-second ceiling — keeps the bot responsive early, since most verification mail lands within the first thirty seconds, without turning into a background loop that hammers the API for five minutes straight. The DELETE /inbox call at the end frees the slot immediately instead of waiting for the address to expire on its own. If you want the same polling shape wired into a test runner instead of a Discord DM, OTP test automation with CyberTemp's API covers that version in more detail.

Concurrent users and plan limits

A bot serving a whole server is a different load pattern than one developer testing a signup flow. Ten people running /burner during a raffle window means ten inboxes open at once, all counted against the same API key's plan. FREE is built for occasional single-user testing, not that. ECO and CORE raise the concurrent-inbox ceiling enough for a small-to-mid server; ELITE is the one to reach for if the bot serves a large community or runs several servers off one key.

Check GET /getPlan before assuming there's headroom — it returns the current tier, limits, and usage for the authenticated key, so the bot can see how close it is to the ceiling instead of finding out from a string of 403 UPGRADE_REQUIRED responses in the middle of an event. The same one-address-per-slot discipline shows up in Airdrop farming with temp mail — different use case, same underlying constraint: more concurrent addresses means a higher tier, not a workaround.

Guardrails before it goes live

A slash command that provisions an inbox on demand is also a command that, without limits, will happily provision hundreds of them in a minute. A few things are worth locking down before the bot goes anywhere near a public server:

  • Put the command behind a role, or add a per-user cooldown. An unthrottled provisioning command burns through plan quota fast, and it's the first thing someone will poke at out of curiosity.

  • Never let a user-facing response echo the API key back. Read it from an environment variable on the bot's host — not a config value a moderator can view through Discord.

  • Log address issuance somewhere the bot owner can audit: timestamp, requesting user ID, address. If a slot limit gets hit mid-event, that log is the fastest way to see who's using it and how.

  • Delete inboxes after delivery instead of letting them pile up. Free and lower paid tiers cap concurrent inboxes, and an idle inbox that already delivered its code is wasted headroom.

None of this is exotic — it's the same operational hygiene a CI signup-flow test needs, just pointed at a Discord channel instead of a test runner.

Who should build this

This is worth building the moment a Discord community's account-creation workflow involves more than one person running it by hand — closed-beta invite management, structured multi-account testing, raffle and drop coordination. It's not worth it for a server that occasionally needs one throwaway address; that's still faster done directly in the browser.

If the bot is about to serve a live event window instead of a handful of manual tests, check the pricing page first — the concurrent-inbox ceiling by tier is what decides whether /burner survives a busy raffle or starts throwing UPGRADE_REQUIRED errors halfway through.

Final answer: build the bot when the manual relay is the bottleneck, not before.

Share