Replit Agent builds apps from a description: it writes the code, sets up the database and hosting, tests what it built, and publishes it. What it cannot do on its own is read an inbox. That gap shows up in two places. The app it builds often sends email (signup confirmation, password reset, magic links), and nothing in the build loop can check that those emails arrive and work. And some apps need to receive email themselves, which needs an address the app controls.
This post covers both with a Lumbox inbox, wired in the way Replit documents for any third-party API.
How Lumbox fits into a Replit app
Replit groups integrations into Replit-managed services, first-party connectors you sign into once, and external integrations where you ask Agent to set up a service and provide the API key yourself. Lumbox is not in the connector catalog, so it is the third kind. Two things to give Agent:
- The key, as a Secret named
LUMBOX_API_KEY. Replit encrypts secrets and exposes them to the app as environment variables. Published apps read their own set of production secrets, so add the key there too before publishing, and note that secrets are not available to Static Deployments. - A short description of the endpoints in your prompt, for example: "Lumbox REST API at
https://api.lumbox.co/v1, auth headerX-API-Key.POST /inboxescreates an address,GET /inboxes/:id/otp?timeout=90&since=ISOwaits for a verification code,DELETE /inboxes/:idremoves it. Call it only from server code."
The last sentence of that prompt matters. A key used in browser code is visible to every visitor, so the Lumbox calls belong in the app's backend or in scripts.
Checking the app's own signup email
Replit's App Testing drives the app in a real browser and enters mock data where it needs to. When a flow needs something outside the app, the documented example being a login to a mailbox, Agent asks you to take over the preview, and if nobody responds within 10 minutes it skips that part of the test. A signup that says "check your email for a code" is exactly that kind of step, so the part most likely to break (the email never being sent, or the code not matching) is the part that goes untested.
A small script closes the gap, and Agent can write it, run it from the shell, and re-run it after every change:
// scripts/check-signup.ts (run with the app running and APP_URL set)
const LUMBOX = "https://api.lumbox.co/v1";
const APP = process.env.APP_URL!;
const lumboxHeaders = { "X-API-Key": process.env.LUMBOX_API_KEY!, "Content-Type": "application/json" };
async function main() {
const inbox = await (await fetch(`${LUMBOX}/inboxes`, { method: "POST", headers: lumboxHeaders, body: "{}" })).json();
try {
const since = new Date(Date.now() - 5000).toISOString();
const signup = await fetch(`${APP}/api/signup`, { // your app's route
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: inbox.address, password: "correct-horse-battery" }),
});
if (!signup.ok) throw new Error(`signup returned ${signup.status}`);
const qs = new URLSearchParams({ timeout: "90", since });
const otp = await fetch(`${LUMBOX}/inboxes/${inbox.id}/otp?${qs}`, { headers: lumboxHeaders });
if (otp.status === 408) throw new Error("no verification email within 90 seconds");
const { code } = await otp.json();
const verify = await fetch(`${APP}/api/verify`, { // your app's route
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: inbox.address, code }),
});
if (!verify.ok) throw new Error(`verify returned ${verify.status}`);
console.log("signup flow OK for", inbox.address);
} finally {
await fetch(`${LUMBOX}/inboxes/${inbox.id}`, { method: "DELETE", headers: lumboxHeaders });
}
}
main().catch((err) => { console.error(err.message); process.exit(1); });
Each run gets a fresh address on trylumbox.com, so the test never collides with a real user or a previous run, and the finally block deletes it. Inboxes do not expire by themselves, and the Free plan holds three at a time.
The since timestamp scopes the wait to mail that arrived after the signup request. The OTP endpoint returns the newest verification email received after it, or looks back five minutes when it is missing. A 408 after 90 seconds is the useful failure: it means your app never sent the email, sent it somewhere else, or sent something the parser does not read as a code.
That parser looks for 4 to 8 digit codes near words like "code" or "verification", in emails categorised as verification mail. If your app sends a magic link instead, wait on GET /v1/inboxes/:id/wait with the same since and read parsed.verification_links (or parsed.magic_links) from the returned email, then request that URL in the script.
When the app itself receives email
Some apps Agent builds need an inbox of their own: a support tool that turns incoming mail into tickets, a bot that reads receipts, one address per customer workspace. The app creates inboxes with POST /v1/inboxes and learns about new mail from a webhook. Create one with POST /v1/webhooks pointing at a route on your published app, keep the whsec_ secret it returns (shown once) as another Replit Secret, and have the route verify X-Lumbox-Signature, an HMAC-SHA256 hex digest of the raw request body keyed with that secret, before trusting anything in it. Webhooks are per org, so the payload's inbox_id tells the app which inbox the mail is for.
Two production details to tell Agent about. Webhook deliveries for received mail are not retried today, so the app should also catch up periodically by listing each inbox's emails with since set to the last time it checked. And the webhook payload has the sender, subject, category and extracted codes but not the body; fetch GET /v1/emails/:id when the app needs the text.
A custom domain for the app's address
An address like support@yourapp.com reads better to your users than a shared-domain address. The flow is three calls: POST /v1/domains with your domain returns the DNS records to add (a verification TXT record plus the MX, SPF and DKIM records), you add them wherever your domain's DNS is hosted, and POST /v1/domains/:id/verify checks them. The domain becomes ACTIVE once the verification TXT, MX and SPF records are found, and inboxes can then be created on it with domain in the create body.
Pointing a domain's MX records at Lumbox sends all of that domain's mail to Lumbox. If you already receive mail on yourapp.com with another provider, use a subdomain such as mail.yourapp.com for the app's inboxes instead of moving the root domain. Replit Domains handles pointing a custom domain at your published app, which is separate from these mail records.
The quickstart covers getting a key, the domains docs list the record types, the webhooks docs describe the payload, and custom domains for agents goes through the DNS side in more detail.