Skip to content

CI with GitHub Actions

Your CI runs your app, your app sends mail to simmail, and your tests wait for it. Nothing needs to run inside the CI job except your app and its tests.

  1. Make a universe for CI. On your account page, Universes → New universe, named for example CI. Keeping CI separate means its clock jumps and test people never touch Staging.
  2. Create a login for your app under Settings → Mail logins in that universe.
  3. Create an API key for that universe with the Member role.
  4. Add secrets to your GitHub repository (Settings → Secrets and variables → Actions):
SecretValue
SIMMAIL_API_KEYThe key, smk_…
SMTP_USERThe login’s address
SMTP_PASSThe login’s password
.github/workflows/email-tests.yml
name: Email tests
on: [push, pull_request]
jobs:
email:
runs-on: ubuntu-latest
env:
SIMMAIL_URL: https://c1.simmail.dev
SIMMAIL_API_KEY: ${{ secrets.SIMMAIL_API_KEY }}
SMTP_HOST: c1.simmail.dev
SMTP_PORT: "2525"
SMTP_USER: ${{ secrets.SMTP_USER }}
SMTP_PASS: ${{ secrets.SMTP_PASS }}
TEST_RUN: ${{ github.run_id }}-${{ github.run_attempt }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npx playwright test

Use your universe’s host from the account page.

Several CI runs can share one universe at the same time. Put the run id in every address your tests create, so one run never catches another run’s email:

const email = `signup-${process.env.TEST_RUN}-${Date.now()}@yourcompany.test`;
const msg = await mail.waitForEmail({ to: email, subject: "verify", timeout: 30000 });

By default, when your app writes to an address nobody plays, simmail puts a stranger behind it, who may answer. Each stranger counts toward your plan’s people limit and can use AI actions. For sign-up tests that create thousands of addresses, turn strangers off for your test domain.

The recipient rules are one document per universe, and PUT replaces all of it, so read it first, change it, and write it back. This needs an admin key:

Terminal window
curl -s https://c1.simmail.dev/api/recipients -H "Authorization: Bearer $ADMIN_KEY" \
| jq '.domains["yourcompany.test"] = {"create": true, "stranger": "off"}' \
| curl -s -X PUT https://c1.simmail.dev/api/recipients \
-H "Authorization: Bearer $ADMIN_KEY" -H "Content-Type: application/json" -d @-

Mail to @yourcompany.test addresses still lands in a mailbox, so waitForEmail works, but nobody answers it.

  • Use waitForReply with fast: true (the default) instead of waiting for people’s real delays.
  • Freeze or move the clock only in the CI universe.
  • Give waits a timeout well under your test runner’s own timeout, so a missing email fails with a clear 408 rather than a hung job.