Guide

Send Email from CircleCI Pipelines

Send build and deploy email alerts from CircleCI with Nylas CLI. Authenticate from a context env var and email on failure with a when: on_fail step, no SMTP host or orb.

Written by Aaron de Mello Senior Engineering Manager

Reviewed by Qasim Muhammad

VerifiedCLI 3.1.17 · Gmail, Outlook · last tested June 9, 2026

You want a failed CircleCI build to email the people who can act on it, and you do not want to register an SMTP host, store a mail password, or pull in a third-party orb. The Nylas CLI sends the CircleCI email notification from one job step: store an API key in a context, install the binary, and call nylas email send behind a when: on_fail guard. The same job runs on the default Docker executor and on a macOS runner.

How do I authenticate the CLI in a CircleCI job?

A CircleCI job authenticates the CLI by reading an API key from an environment variable supplied by a context. Store the key as a context variable named NYLAS_API_KEY, set NYLAS_GRANT_ID for the sender account, and the binary picks both up automatically. No browser OAuth flow runs in CI, so the headless path is the only one that works.

CircleCI contexts hold shared secrets across projects in an organization. A context is scoped by security group, so only approved projects can read NYLAS_API_KEY. That is cleaner than per-project environment variables when several pipelines send through the same automation account, and it lets you rotate one secret instead of editing twenty project settings.

Set NYLAS_DISABLE_KEYRING=true in the job so the tool does not try to open a system keyring that does not exist on an ephemeral runner. The default Docker executor ships 2 vCPUs and 4 GB of RAM with no keyring service, and without that flag the first authenticated call can hang. The grant ID is the account that appears in the From header on every alert.

Set the three values in a context, then export the keyring flag in the job. The CLI reads NYLAS_API_KEY and NYLAS_GRANT_ID from the environment with no extra auth config call:

# Set once in the CircleCI context "nylas-ci":
#   NYLAS_API_KEY   = nyk_...
#   NYLAS_GRANT_ID  = <sender grant id>
#
# In the job, disable the keyring on the ephemeral runner:
export NYLAS_DISABLE_KEYRING=true

# Confirm auth resolves before sending:
nylas email folders list --json | head

How do I email only when a CircleCI job fails?

Add a step with when: on_fail so it runs only after an earlier step exits non-zero. CircleCI evaluates the when attribute per step: on_fail runs when any prior step in the job failed, on_success runs when all prior steps passed, and always runs in both cases. One on_fail step at the end of a job sends a single alert instead of one per command.

Keep the notify step last in the job. Earlier steps run the build, the tests, or the deploy, and the email step reports the outcome. Because on_fail only triggers on a non-zero exit, a green pipeline sends nothing and stays quiet. CircleCI marks the job failed but still runs the on_fail step, so the alert reflects the real status.

Guard the install and send in the same on_fail step to avoid wasting runner minutes on green builds. The 2024 CircleCI State of Software Delivery report measured a median recovery time of roughly one hour after a failed default-branch run, so a fast, targeted email shortens the window before a human sees the break.

The send command runs inside the on_fail step. The grant ID is the first positional argument, --to sets the recipient, and --yes skips the confirmation prompt no runner can answer:

nylas email send "$NYLAS_GRANT_ID" \
  --to "$ALERT_EMAIL_TO" \
  --subject "CircleCI failed: $CIRCLE_PROJECT_REPONAME ($CIRCLE_BRANCH)" \
  --body "Build $CIRCLE_BUILD_NUM failed. Run: $CIRCLE_BUILD_URL" \
  --yes \
  --json

What does a working .circleci/config.yml look like?

A working config defines one job that runs your build, then a final step with when: on_fail that installs the CLI and sends one email. The workflow attaches the context that holds NYLAS_API_KEY and NYLAS_GRANT_ID. CircleCI reads .circleci/config.yml from the repository root on every push, and version 2.1 has been the current schema since 2019.

The install script runs inside the job because CircleCI Docker executors start from a clean image on every run. It auto-detects the architecture, downloads the latest release from GitHub, and verifies the binary with a SHA-256 checksum in a few seconds. See getting started for Homebrew and other install methods.

CircleCI exposes built-in variables such as CIRCLE_PROJECT_REPONAME, CIRCLE_BRANCH, CIRCLE_SHA1, and CIRCLE_BUILD_URL inside every job. Pass CIRCLE_BUILD_URL in the body so the recipient opens the exact failed run. Without --yes the send blocks forever and the job times out:

version: 2.1

jobs:
  build:
    docker:
      - image: cimg/node:22.11
    steps:
      - checkout
      - run: npm ci
      - run: npm test

      - run:
          name: Email on failure
          when: on_fail
          command: |
            export NYLAS_DISABLE_KEYRING=true
            curl -fsSL https://cli.nylas.com/install.sh | bash
            export PATH="$HOME/.config/nylas/bin:$PATH"
            nylas email send "$NYLAS_GRANT_ID" \
              --to "$ALERT_EMAIL_TO" \
              --subject "CircleCI failed: $CIRCLE_PROJECT_REPONAME" \
              --body "Run: $CIRCLE_BUILD_URL" \
              --yes --json

workflows:
  ci:
    jobs:
      - build:
          context:
            - nylas-ci

How do I send a richer HTML build report?

Write a short HTML file in the job and pass its contents to --body — the flag accepts HTML or plain text and the CLI auto-detects which, so the tool sends a rich message. This keeps the YAML readable and lets you include a heading, the failing branch, the commit SHA, and a link to the run. A scannable report beats a wall of raw log output.

Keep the HTML small: a heading, two or three lines, and one link. Do not paste full build logs into the email, because CircleCI already stores logs for 30 days on the run page. The email points to that page; it does not replace it. A 10-second read with one click to the run is the target.

Use --track-opens when you must confirm an on-call engineer saw a high-severity alert. The CLI records an open event you can query later, which helps incident reviews where you prove the notification path worked. Reserve tracking for high-severity streams so routine build emails stay lightweight:

cat > report.html <<'HTML'
<h1>CircleCI build failed</h1>
<p>Branch failed during the test step. Open the run for logs.</p>
HTML

nylas email send "$NYLAS_GRANT_ID" \
  --to "$ALERT_EMAIL_TO" \
  --subject "Build failed: $CIRCLE_PROJECT_REPONAME" \
  --body "$(cat report.html)" \
  --track-opens \
  --yes --json

Why run a separate channel test for CircleCI alerts?

Run a manual pipeline that sends a test email so you verify the alert path without breaking a real build on purpose. CircleCI triggers a pipeline from the API or the UI with a parameter, and a parameter-gated job can send one message with a clear subject like CircleCI email channel test. Run it after rotating the API key or changing the recipient list.

Track send failures separately from build failures. If a deploy fails and the email step also fails, you need both facts. Write the command output to the job with --json so the returned message ID lands in the CircleCI log. That gives engineering a way to confirm the alert was sent without searching inboxes manually.

Rotate the context secret on a schedule, such as every 90 days, and after team changes. A dedicated automation sender makes revocation clean: update one context variable, run the test pipeline, and confirm the message lands. Keep that drill separate from production failure alerts so a smoke test never reads like a real incident to the people on call.

# Parameter-gated smoke test, triggered manually:
nylas email send "$NYLAS_GRANT_ID" \
  --to "$ALERT_EMAIL_TO" \
  --subject "CircleCI email channel test" \
  --body "Channel verified at run $CIRCLE_BUILD_URL" \
  --yes --json

Next steps