Skip to content

GitHub Actions

Guide for verifying outbound mail in GitHub Actions with Mailexam. Your application sends mail over SMTP; the pipeline waits for delivery and asserts on subject and body via mailexam-cli.

Reference repository: github.com/mailexam/GitHub-Actions

What you need

  • A Mailexam account and a project with SMTP credentials.
  • An API token from the dashboard — for the CLI only, not for SMTP.
  • A GitHub repository with Actions enabled.

Copy from the welcome email (or dashboard) for your project:

  • YOUR_LOGIN — SMTP login (for example, xxxxx);
  • YOUR_PASSWORD — SMTP password (a unique pair with the login);
  • host — YOUR_LOGIN.mailexam.io (matches the login).

Why Mailexam in CI

On a shared GitHub runner there is no local Mailpit or MailHog next to your app. Mailexam receives SMTP from any runner and exposes messages in the browser and via the REST API. See MailHog/Mailpit vs Mailexam.

Secrets and variables

Settings → Secrets and variables → Actions.

Name Description
MAILEXAM_API_TOKEN API token for mailexam-cli
MAILEXAM_LOGIN SMTP login from the welcome email
MAILEXAM_PASSWORD SMTP password (pair with the login)
Name Description
MAILEXAM_PROJECT_UUID Project UUID — dashboard or mailexam project list

SMTP vs API token

MAILEXAM_LOGIN / MAILEXAM_PASSWORD are for your app to send mail. MAILEXAM_API_TOKEN is for the CLI to read mail — do not mix them.

Pull requests from forks

GitHub does not expose repository secrets to workflows triggered by fork PRs. Use workflow_dispatch, pushes to your own branches, or a dedicated test repository.

Typical flow

sequenceDiagram
    participant App as Your app / script
    participant SMTP as Mailexam SMTP
    participant GHA as GitHub Actions
    participant CLI as mailexam CLI
    participant API as REST API

    GHA->>App: run tests / send mail
    App->>SMTP: sendMail()
    Note over SMTP: Message in sandbox
    GHA->>CLI: mailexam email assert
    CLI->>API: polling GET /email
    API-->>CLI: message found
    CLI->>CLI: verify subject, body
    Note over CLI: exit 0 or 1
  1. A job step sends mail via SMTP (your app or a test script).
  2. Another step runs mailexam email wait or mailexam email assert.
  3. Non-zero exit code fails the job — no wrapper script needed.

Reference workflow

The full working example is in mailexam/GitHub-Actions:

name: Mailexam email test

on:
  workflow_dispatch:
  push:
    branches: [main]

jobs:
  email-test:
    runs-on: ubuntu-latest
    env:
      MAILEXAM_API_BASE: https://mailexam.io/api/v1
      MAILEXAM_PROJECT_UUID: ${{ vars.MAILEXAM_PROJECT_UUID }}
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm ci

      - name: Send test email
        env:
          MAILEXAM_LOGIN: ${{ secrets.MAILEXAM_LOGIN }}
          MAILEXAM_PASSWORD: ${{ secrets.MAILEXAM_PASSWORD }}
          MAILEXAM_PORT: '587'
        run: npm run send-test-email

      - uses: actions/setup-go@v5
        with:
          go-version: '1.22'

      - name: Install mailexam CLI
        run: go install github.com/mailexam/mailexam-cli/cmd/mailexam@latest

      - name: Add Go bin to PATH
        run: echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"

      - name: Assert email delivered
        env:
          MAILEXAM_API_TOKEN: ${{ secrets.MAILEXAM_API_TOKEN }}
        run: >
          mailexam email assert
          --subject "GitHub Actions + Mailexam"
          --contains "Mailexam CI check"

Fork or copy .github/workflows/mailexam.yml and scripts/send-test-email.js into your repository.

SMTP for your application

Pass SMTP credentials as job env so your tests use Mailexam instead of a real mailbox:

env:
  MAILEXAM_LOGIN: ${{ secrets.MAILEXAM_LOGIN }}
  MAILEXAM_PASSWORD: ${{ secrets.MAILEXAM_PASSWORD }}
  MAIL_HOST: ${{ secrets.MAILEXAM_LOGIN }}.mailexam.io
  MAIL_PORT: '587'

Framework-specific .env mapping: integration examples (Laravel, Django, Express, and others).

mailexam-cli in the pipeline

Install Go 1.22+, then:

- uses: actions/setup-go@v5
  with:
    go-version: '1.22'
- run: go install github.com/mailexam/mailexam-cli/cmd/mailexam@latest
- run: echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"

Common assert:

mailexam email assert --subject "Order confirmed" --contains "Thank you"
mailexam email assert --subject "Code" --contains "123456" --timeout 60

Command reference and exit codes: Command-line interface (CLI).

Environments (optional)

For staging vs production, create GitHub Environments (Settings → Environments) with different secrets and MAILEXAM_PROJECT_UUID per environment:

jobs:
  email-test:
    runs-on: ubuntu-latest
    environment: staging

Troubleshooting

api token is required

  • Add MAILEXAM_API_TOKEN to repository secrets or pass --token.

Exit code 2 (403)

Exit code 3 (timeout)

  • Increase --timeout — SMTP delivery can take a few seconds on cold runners.
  • Check that MAILEXAM_PROJECT_UUID matches the project whose SMTP login you use.

mailexam: command not found

  • Add $(go env GOPATH)/bin to PATH after go install (see workflow above).

SMTP connection errors

  • Host must be YOUR_LOGIN.mailexam.io — same login as in MAILEXAM_LOGIN, without an smtp. prefix.
  • Use port 587 with STARTTLS.

See also