# Build an Uptime Monitor with curl, poller, jobs, cron, and email ![banner](https://articles.aux4.blog/data/posts/aux4/uptime-monitoring-with-curl-poller-cron-and-email/banner.png) ## Introduction This post builds a self-hosted uptime monitor and bottles it into a single command: `aux4 monitor `. You'll make an HTTP request with `curl`, retry it with `poller`, run the check as a background job with `jobs` that emails an alert through `email` on failure, package that into your own `monitor` command, and schedule it with `cron`. The result probes a site every few minutes and emails you the moment it stops responding — no monitoring SaaS required. ## Install the packages ```bash aux4 aux4 pkger install aux4/curl aux4/poller aux4/jobs aux4/cron aux4/email ``` ## Step 1: make the request `aux4 curl request` is a pipe-friendly HTTP client. By default it prints only the response body: ```bash aux4 curl request https://example.com ``` ```text Example Domain... ``` Add `--showHeaders true` to see the status line and headers: ```bash aux4 curl request --showHeaders true https://example.com | head -1 ``` ```text HTTP/2.0 200 OK ``` That first line is your signal: `200` means healthy, anything else (or a connection error) means trouble. ## Step 2: poll until healthy (or give up) A single request tells you the state right now, but a blip isn't an outage. `aux4 poll` runs a command on an interval until the output matches a success pattern, hits a failure pattern, or times out: ```bash aux4 poll \ --command="aux4 curl request --showHeaders true https://example.com | head -1" \ --expectation="*200*" \ --maxWait=20 --interval=5 ``` ```text [0 s] HTTP/2.0 200 OK Complete ``` The flags: - `--command` — the command to run each cycle. - `--expectation` — the success pattern, matched with shell glob rules: `*200*` matches output containing `200`. Use a pattern with no spaces. - `--maxWait` — total seconds to keep trying (default `900`). - `--interval` — seconds between attempts (default `30`). Each cycle prints `[ s] `. On success it prints `Complete` and exits `0`; on timeout it prints `Timeout` and exits `1`: ```text [0 s] HTTP/2.0 503 Service Unavailable [5 s] HTTP/2.0 503 Service Unavailable Timeout ``` Because `poll` keeps retrying until `maxWait`, a single blip never alerts — only a sustained outage does. `--interval` and `--maxWait` set how many attempts to allow first: `--interval=20 --maxWait=60` checks at 0s, 20s, and 40s — three strikes before the check fails. That non-zero exit is what makes it composable. You can also fail fast on a known-bad response with `--failValue`: ```bash aux4 poll \ --command="aux4 curl request --showHeaders true https://example.com | head -1" \ --expectation="*200*" \ --failValue="*503*" \ --maxWait=120 --interval=15 ``` ## Step 3: alert by email `aux4 email send` sends mail over SMTP. Credentials live in a YAML config file — by default `~/.config/aux4/email.yaml` — so no password ends up on the command line: ```yaml imap: host: imap.gmail.com port: "993" smtp: host: smtp.gmail.com port: "465" user: alerts@yourdomain.com password: xxxx xxxx xxxx xxxx ``` > For Gmail, Outlook, or Yahoo, use an app-specific password. The sender address is the `user:` field — there is no `--from` flag. Sending is a one-liner: ```bash aux4 email send \ --to ops@yourdomain.com \ --subject "ALERT: example.com health check failed" \ --body "The health endpoint did not return 200 in time." ``` On success it prints the message ID: ```text Email sent successfully. Message ID: ``` ## Step 4: alert on failure with a job hook `poll` exits non-zero when the check fails. Hand the whole check to `aux4 jobs run`: it runs the command in the background and fires a hook based on the exit code. `--onFailure` sends the email only when the poll fails — no shell `||` glue, and every run is recorded: ```bash aux4 jobs run \ --command "aux4 poll --command='aux4 curl request --showHeaders true https://example.com | head -1' --expectation='*200*' --maxWait=20 --interval=5" \ --onFailure "aux4 email send --to ops@yourdomain.com --subject 'ALERT: example.com is down' --body 'Health check failed.'" ``` It returns the job immediately: ```json { "id": "1", "state": "RUNNING", "exitCode": 0, "onFailure": "aux4 email send ..." } ``` `jobs list` shows how it ended — `COMPLETED` when the site is up (no email), `FAILED` when it isn't (the hook fired the alert): ```bash aux4 jobs list ``` ```json [ { "id": "1", "state": "COMPLETED", "exitCode": 0, "command": "aux4 poll ..." } ] ``` ## Step 5: bottle it into one command That one-liner does the job, but you don't want to retype it. Wrap it in an `.aux4` file so it becomes a command with the URL as an argument. Create `.aux4`: ```json { "profiles": [ { "name": "main", "commands": [ { "name": "monitor", "execute": [ "aux4 jobs run --command \"aux4 poll --command='aux4 curl request --showHeaders true ${url} | head -1' --expectation='*200*' --maxWait=${maxWait} --interval=${interval}\" --onFailure \"aux4 email send --to ${email} --subject 'ALERT: ${url} is down' --body 'Health check failed.'\"" ], "help": { "text": "Monitor a URL and email an alert if it stops responding", "variables": [ { "name": "url", "text": "URL to monitor", "arg": true }, { "name": "email", "text": "Alert recipient", "default": "ops@yourdomain.com" }, { "name": "interval", "text": "Seconds between retries", "default": "10" }, { "name": "maxWait", "text": "Max seconds to wait for a healthy response", "default": "60" } ] } } ] } ] } ``` aux4 picks up the `.aux4` in your working directory, so the command is ready: ```bash aux4 monitor https://example.com ``` ```json { "id": "1", "state": "RUNNING", "command": "aux4 poll ...", "onFailure": "aux4 email send ..." } ``` The URL is a positional argument; `--email`, `--interval`, and `--maxWait` have defaults you can override. Install it as a package to run `aux4 monitor` from any directory. ## Step 6: run it on a schedule With the whole check behind one command, scheduling it is trivial — point `cron` at `aux4 monitor`: ```bash aux4 cron start aux4 cron add --name uptime-check --every "5 min" --run "aux4 monitor https://example.com" ``` ```json { "name": "uptime-check", "every": "5 min", "run": "aux4 monitor https://example.com", "state": "active" } ``` Each firing is recorded in the history with its job id: ```bash aux4 cron history --name uptime-check ``` ```json [ { "name": "uptime-check", "jobId": "1", "timestamp": "2025-11-19T14:00:00Z", "status": "TRIGGERED" } ] ``` Pause, resume, or remove it without restarting the daemon: ```bash aux4 cron pause --name uptime-check aux4 cron resume --name uptime-check aux4 cron remove --name uptime-check ``` ## Conclusion You built `aux4 monitor ` out of five single-purpose packages: - **curl** makes the request, - **poller** turns a one-shot check into a patient retry loop with a clean exit code, - **jobs** runs that check in the background and fires the alert on failure, - **email** delivers the alert, - **cron** runs the command on a schedule and keeps a history. The composition lives in one small `.aux4` file, so `aux4 monitor https://example.com` is all anyone has to remember. Swap the URL for an internal service, point the expectation at a JSON field, or schedule a dozen of them — the command stays the same. ## See Also - [aux4/curl](https://hub.aux4.io/r/public/packages/aux4/curl) - [aux4/poller](https://hub.aux4.io/r/public/packages/aux4/poller) - [aux4/jobs](https://hub.aux4.io/r/public/packages/aux4/jobs) - [aux4/cron](https://hub.aux4.io/r/public/packages/aux4/cron) - [aux4/email](https://hub.aux4.io/r/public/packages/aux4/email)