Skip to content

JavaScript and Python SDKs

Both clients are thin wrappers over the HTTP API, with no dependencies. Both read their settings from the environment:

VariableMeaningDefault
SIMMAIL_URLThe server address, without /api, such as https://c1.simmail.dev.http://127.0.0.1:8025
SIMMAIL_API_KEYYour API key, smk_….none
SIMMAIL_UNIVERSESent as X-Simmail-Universe. Not needed with an API key.none

Errors from the API raise SimmailError, which has the HTTP status and the API’s error message.

@plexuslabs/simmail works in Node 18 or newer and ships TypeScript types.

Terminal window
npm install @plexuslabs/simmail
import { test, expect } from "@playwright/test";
import { Simmail } from "@plexuslabs/simmail";
const mail = new Simmail(); // reads SIMMAIL_URL and SIMMAIL_API_KEY
test("new users can verify their email", async ({ page }) => {
const email = `user-${Date.now()}@yourcompany.test`;
await page.goto("/signup");
await page.fill("#email", email);
const waiting = mail.waitForEmail({ to: email, subject: "verify", timeout: 20000 });
await page.click("text=Create account");
const msg = await waiting;
expect(msg.codes).toHaveLength(1);
await page.fill("#code", msg.codes[0]); // or: await page.goto(msg.magic_links[0])
});

Start the wait before the click: a wait only sees mail that arrives after it starts, unless you pass existing: true.

const sent = await mail.send({
subject: "Offer",
text: "Would you take 165k?",
});
const reply = await mail.waitForReply({ from: "[email protected]", inReplyTo: sent.message_id });
console.log(reply.text);
MethodAPI call
new Simmail({ baseUrl, apiKey, universe })Every option falls back to the environment.
waitForEmail({ to, from, subject, contains, folder, existing, timeout })GET /api/wait. timeout is milliseconds (default 30000) or a string such as "45s".
waitForReply({ from, inReplyTo, fast, timeout })GET /api/wait/reply. fast defaults to true.
extract({ text, html })POST /api/extract
send({ from, to, cc, subject, text, html })POST /api/send. Returns { message_id, status }.
health(messageId)GET /api/inspect
personas()GET /api/personas
createPersona(p)POST /api/personas
pushCase(id, kase)PUT /api/cases/{id}
clock()GET /api/clock
advance(duration)POST /api/clock/advance, such as advance("2d")
freeze()POST /api/clock/freeze
speed(factor)POST /api/clock/speed
request(method, path, body, query)Any other endpoint.

The simmail package works with Python 3.9 or newer and uses only the standard library.

from simmail import Simmail
mail = Simmail() # reads SIMMAIL_URL and SIMMAIL_API_KEY
msg = mail.wait_for_email("[email protected]", subject="verify", timeout=20)
code = msg["codes"][0] # or msg["magic_links"][0]
sent = mail.send("[email protected]", ["[email protected]"], "Offer", text="Would you take 165k?")
reply = mail.wait_for_reply("[email protected]", in_reply_to=sent["message_id"])
print(reply["text"])

Results are plain dictionaries with the API’s field names.

import time
from simmail import Simmail
mail = Simmail()
def test_password_reset(client):
email = f"user-{int(time.time())}@yourcompany.test"
client.post("/signup", data={"email": email, "password": "pw-123456789"})
client.post("/forgot-password", data={"email": email})
msg = mail.wait_for_email(email, subject="reset", existing=True, timeout=20)
assert msg["magic_links"], msg["text"]
MethodAPI call
Simmail(base_url=None, api_key=None, universe=None)Every option falls back to the environment.
wait_for_email(to, *, sender=None, subject=None, contains=None, folder=None, existing=False, timeout=30)GET /api/wait. sender is the from filter. A number timeout is seconds; a string such as "2m" is passed as is.
wait_for_reply(sender, *, in_reply_to=None, fast=True, timeout=30)GET /api/wait/reply
extract(text="", html="")POST /api/extract
send(sender, to, subject, text="", html="", cc=None)POST /api/send
health(message_id)GET /api/inspect
personas()GET /api/personas
create_persona(persona)POST /api/personas
push_case(case_id, case)PUT /api/cases/{id}
advance(duration)POST /api/clock/advance
freeze()POST /api/clock/freeze
request(method, path, body=None, query=None, timeout=330)Any other endpoint. timeout is the HTTP timeout in seconds.