JavaScript and Python SDKs
Both clients are thin wrappers over the HTTP API, with no dependencies. Both read their settings from the environment:
| Variable | Meaning | Default |
|---|---|---|
SIMMAIL_URL | The server address, without /api, such as https://c1.simmail.dev. | http://127.0.0.1:8025 |
SIMMAIL_API_KEY | Your API key, smk_…. | none |
SIMMAIL_UNIVERSE | Sent 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.
JavaScript
Section titled “JavaScript”@plexuslabs/simmail works in Node 18 or newer and ships TypeScript types.
npm install @plexuslabs/simmailA sign-up test with Playwright
Section titled “A sign-up test with Playwright”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.
A conversation
Section titled “A conversation”const sent = await mail.send({ subject: "Offer", text: "Would you take 165k?",});console.log(reply.text);Methods
Section titled “Methods”| Method | API 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. |
Python
Section titled “Python”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
code = msg["codes"][0] # or msg["magic_links"][0]
print(reply["text"])Results are plain dictionaries with the API’s field names.
A pytest example
Section titled “A pytest example”import timefrom 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"]Methods
Section titled “Methods”| Method | API 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. |