Quickstart
Five minutes from nothing to a test that signs a user up and reads the code out of their inbox.
1. Get a key
Create a developer account, pick a plan, then open your account and create an API key. The key is shown once, so put it somewhere safe immediately.
For CI, create a key with the controls set: give it an expiry date, and if your runners have fixed addresses, an IP allowlist. A key that can only be used from your CI is a much smaller problem if it ever leaks.
2. Install the client
npm install mailfornet
You can call the API with plain fetch if you prefer — it's ordinary REST. The client just
saves you writing the polling, the retries and the code extraction yourself.
3. Keep the key out of your code
export MAILFORNET_API_KEY="mfk_..."
The client reads MAILFORNET_API_KEY automatically. In CI, add it as a secret rather than
committing it.
4. Write the test
import Mailfornet from 'mailfornet';
const mf = new Mailfornet();
test('a new user can confirm their email', async () => {
// A fresh inbox, so parallel runs never collide
const inbox = await mf.createInbox();
await signUp(inbox.address);
// Holds the connection open until the mail lands — no polling loop
const code = await mf.waitForCode(inbox.address, { timeout: 60 });
await confirmEmail(code);
expect(await isConfirmed(inbox.address)).toBe(true);
});
5. Be specific about which mail you want
If your app sends more than one email, filter, so the test doesn't grab whichever arrived first:
const code = await mf.waitForCode(inbox.address, {
from: 'noreply@yourapp.com',
subject: 'Confirm your email',
timeout: 60,
});
Need the whole message rather than a code — to check a link, or the HTML?
const message = await mf.waitForMessage(inbox.address, { subject: 'Reset' });
const link = message.html.match(/https:\/\/[^"]+\/reset\/[^"]+/)[0];
6. Clean up (optional)
await mf.deleteInbox(inbox.address);
Inboxes expire by themselves, so this is only worth doing if you want the address released sooner.
Things worth knowing early
- Waiting is one request. A 60-second long-poll costs one request, not sixty. Polling in a loop is what burns a quota.
- Retries are safe. Pass an
idempotencyKeytocreateInboxand a retried call returns the first inbox instead of making a second. - Branch on
code, not the message. Error messages get reworded; codes likequota_exceededdon't. - Log the request id. Every response carries one. It's the fastest way for us to find what happened.
Next
The full API reference covers every endpoint, webhooks, authentication, limits and the OpenAPI spec. Pricing explains what counts as a request.