Source: https://cli.nylas.com/guides/kubernetes-cronjob-email

# Send Email from a Kubernetes CronJob

A digest at 8am, an alert when a backup finishes, a weekly report — these are CronJobs, and they need to send email. Running an SMTP sidecar in a cluster is overkill, and embedding a mailbox password in a manifest is a security review failure. The Nylas CLI sends over API, so a CronJob calls one command with the API key pulled from a Secret. This guide gives a hardened CronJob manifest with a non-root container, a Secret-backed key, and resource limits.

Written by [Aaron de Mello](https://cli.nylas.com/authors/aaron-de-mello) Senior Engineering Manager

Updated June 8, 2026

> **TL;DR:** Run a Kubernetes CronJob whose container has the Nylas CLI, pull the API key from a Secret with `secretKeyRef`, and call `nylas auth config --api-key` then `nylas email send`. Harden the pod: `runAsNonRoot`, `readOnlyRootFilesystem`, dropped capabilities, and resource limits. No SMTP sidecar, no mailbox password in the manifest.

Command references used in this guide: [`nylas email send`](https://cli.nylas.com/docs/commands/email-send), [`nylas auth config`](https://cli.nylas.com/docs/commands/auth-config), and [`nylas email list`](https://cli.nylas.com/docs/commands/email-list).

## How do you send email from a Kubernetes CronJob?

You send email from a CronJob by running a container that has the Nylas CLI, authenticating with an API key, and calling `nylas email send` on the schedule. Because the CLI sends over HTTPS, the pod needs no SMTP sidecar and no mail daemon — the job is a single short-lived container that exits after sending. A Kubernetes `CronJob` runs it on a cron schedule, per the [Kubernetes CronJob docs](https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/).

Bake the CLI into a small image so the job starts fast rather than installing on every run — a slim base plus the install script in the Dockerfile keeps the image lean. The API key comes from a Kubernetes Secret, never the manifest, so the credential is managed separately from the workload definition and can be rotated without redeploying the CronJob.

```dockerfile
# Build a tiny image with the CLI baked in (multi-stage, slim runtime)
# Dockerfile
FROM debian:stable-slim
RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates \
  && curl -fsSL https://cli.nylas.com/install.sh | bash \
  && apt-get purge -y curl && rm -rf /var/lib/apt/lists/*
ENV PATH="/root/.config/nylas/bin:${PATH}"
USER 10001
```

## How do you keep the API key out of the manifest?

Keep the key out of the manifest by storing it in a Secret and referencing it with `secretKeyRef`, which injects it as an environment variable at runtime. The CronJob spec names the Secret; the key value lives only in the Secret object, ideally managed by an external secrets controller rather than committed YAML. This is the difference between a manifest you can put in Git and one you can't.

Create the Secret once, then reference it from the job. Never inline the key in `env.value` or a ConfigMap — those are not secret stores. For production, source the Secret from a manager like External Secrets so the value is never in your repo at all, and grant the CronJob's service account read access to only that Secret.

```bash
# Create the Secret (or sync it from a secrets manager)
kubectl create secret generic nylas-api \
  --from-literal=api-key="$NYLAS_API_KEY"
```

## What does the hardened CronJob manifest look like?

The manifest runs the CLI image on a schedule with a locked-down security context: non-root user, read-only root filesystem, all Linux capabilities dropped, and explicit resource requests and limits. These controls aren't optional hardening theater — a CronJob that sends email needs no write access to its filesystem and no elevated privileges, so denying them shrinks the attack surface to nothing useful.

The container command authenticates from the injected key and sends. Set `restartPolicy: Never` and a sensible `backoffLimit` so a failed send retries a bounded number of times instead of looping forever. The schedule below runs daily at 08:00; adjust the cron expression for your cadence.

```yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-email
spec:
  schedule: "0 8 * * *"
  jobTemplate:
    spec:
      backoffLimit: 2
      template:
        spec:
          restartPolicy: Never
          securityContext:
            runAsNonRoot: true
            runAsUser: 10001
            seccompProfile: { type: RuntimeDefault }
          containers:
            - name: send
              image: registry.example.com/nylas-cli:3.1.16
              securityContext:
                allowPrivilegeEscalation: false
                readOnlyRootFilesystem: true
                capabilities: { drop: ["ALL"] }
              resources:
                requests: { cpu: "50m", memory: "64Mi" }
                limits:   { cpu: "200m", memory: "128Mi" }
              env:
                - name: NYLAS_API_KEY
                  valueFrom:
                    secretKeyRef: { name: nylas-api, key: api-key }
              command: ["/bin/sh","-c"]
              args:
                - >
                  nylas auth config --api-key "$NYLAS_API_KEY" &&
                  nylas email send --to team@example.com
                  --subject "Daily report" --body "Generated at 08:00 UTC." --yes
```

## Next steps

- [Cron email without Postfix](https://cli.nylas.com/guides/cron-job-email-without-postfix) — the host-cron equivalent
- [Deploy agent accounts with Docker](https://cli.nylas.com/guides/deploy-agent-accounts-docker) — containerizing the CLI
- [Automate email reports](https://cli.nylas.com/guides/automate-email-reports-terminal) — what to send on a schedule
- [Send email from a bash script](https://cli.nylas.com/guides/send-email-from-bash-script) — the command the job runs
- [Jenkins email notifications](https://cli.nylas.com/guides/jenkins-email-notifications) — pipeline post-build email
- [Sync email to S3](https://cli.nylas.com/guides/sync-email-to-s3) — archive mail from a scheduled job
- [Full command reference](https://cli.nylas.com/docs/commands) — every flag and subcommand documented
