# Run the alerts service

> Run the service that warns margin holders by email or Telegram before a weekend and before a liquidation, with its API, environment variables and what it stores.

Source: https://docs.tapehouse.xyz/operate/alerts

The alerts service warns the holder of a margin position before a weekend and before a liquidation, by email or Telegram as the holder chose. It sends no transaction, and it keeps nothing an alert does not need. You can run your own for your users, with your own sender address and bot.

## Run it

You need a registry, an RPC URL, an indexer, a public address for the links in emails, a secret of at least 32 characters, and one channel.

```bash
export ROBINHOOD_TESTNET_RPC_URL=https://rpc.testnet.chain.robinhood.com
export TAPEHOUSE_INDEXER_URL=https://indexer-production-f5dc.up.railway.app
export TAPEHOUSE_ALERTS_URL=https://alerts.example.org
export TAPEHOUSE_ALERTS_SECRET=$(openssl rand -hex 32)
export TAPEHOUSE_SMTP_URL=smtp://user:password@smtp.example.org:587
export TAPEHOUSE_ALERTS_FROM=alerts@example.org
make run-alerts CHAIN=46630
```

`CHAIN` is 4663, 46630, 42161 or 412346, and `REGISTRY=<file>` overrides the registry. The service listens on `127.0.0.1:8090` unless you set `TAPEHOUSE_ALERTS_LISTEN`.

## Environment variables

| Variable | Meaning | Default |
| --- | --- | --- |
| `TAPEHOUSE_DEPLOYMENTS` | Path of the registry file. Required. | none |
| `TAPEHOUSE_RPC_URL` | RPC of the registry's chain. Required. | none |
| `TAPEHOUSE_INDEXER_URL` | Indexer that names the positions. Required. | none |
| `TAPEHOUSE_ALERTS_URL` | The http(s) address the links in emails point to. Required. | none |
| `TAPEHOUSE_ALERTS_SECRET` | At least 32 characters. Each unsubscribe token is derived from it. Required. | none |
| `TAPEHOUSE_RPC_FALLBACK_URLS` | More RPC URLs, separated by commas. | none |
| `TAPEHOUSE_RPC_RATE` | Most requests a second sent to the RPC. | 20 |
| `TAPEHOUSE_ALERTS_DB` | Path of the SQLite file. | `alerts.db` |
| `TAPEHOUSE_ALERTS_LISTEN` | Address to listen on. | `127.0.0.1:8090` |
| `TAPEHOUSE_ALERTS_INTERVAL` | Time between evaluations, a Go duration of at least one second. | `60s` |
| `TAPEHOUSE_SMTP_URL` | SMTP server URL. Goes with `TAPEHOUSE_ALERTS_FROM`. | none |
| `TAPEHOUSE_ALERTS_FROM` | Sender address for email. | none |
| `TELEGRAM_BOT_TOKEN` | Telegram bot token. | none |
| `TELEGRAM_API_URL` | Telegram Bot API base URL. | `https://api.telegram.org` |

At least one channel is required: the SMTP pair, or the bot token. Setting only one of `TAPEHOUSE_SMTP_URL` and `TAPEHOUSE_ALERTS_FROM` is an error.

## How a position is judged

Each position is read through the Go SDK at one block and valued as the liquidator values it: Stock Tokens at their band's low edge, a recalled loan less its haircut, USDG at par, and WETH at 84% of Chainlink's price. A position is left unjudged while the sequencer is unsettled, while an asset it holds is halted or has an unconfirmed multiplier step, or while WETH has no price. The computed requirement must equal the engine's `currentRequirement` at the block's own prices, or the position is not evaluated and nothing is said of it.

For each Stock Token held, with every other asset at its band's centre, an integer bisection finds two prices:

- the weekend price, the highest centre at which the position falls short at both edges of a closed band;
- the reopening price, the highest centre at which it falls short at its low edge against the open requirement, which is where the reopening auction may enrol it.

The accounts' own open-market `liquidationPrice` is shown beside them. See [Liquidation](https://docs.tapehouse.xyz/concepts/liquidation).

## Which alerts it sends

The service evaluates every position on the chain's clock, once for each interval.

| Alert | When |
| --- | --- |
| Threshold | Equity over requirement falls under the subscriber's threshold (1.25 unless set between 1.05 and 3). Sent once, and armed again after the ratio rises 5% above the threshold. |
| Weekend | Once for each closure, when the seven-hour ramp before a weekend or holiday close begins. |
| Liquidation | The position falls short. Once for each time and each auction. |
| Unjudged | Once each time a position cannot be judged, such as during a halt. |

Shorts get threshold and liquidation alerts, not a weekend price. A failed send is tried at the next two ticks and then dropped, logged by subscription ID and never by destination.

## The subscription API

`POST /v1/subscriptions` takes `{account, channel, destination, threshold, issuedAt, signature}`. The signature is an EIP-191 `personal_sign` by the account itself over a fixed text that names the chain, the account, the channel and `issuedAt`, which may be at most ten minutes old.

| Route | What it does |
| --- | --- |
| `POST /v1/subscriptions` | Creates a subscription. |
| `GET /v1/confirm/<token>` | Confirms an email subscription. The link is valid for a day. |
| `GET /v1/unsubscribe/<token>` | Deletes a subscription. It is the link at the foot of every email. |
| `DELETE /v1/subscriptions/<id>` | Deletes a subscription, with a fresh signature. |

A Telegram subscription is confirmed with the code the response returns, opened as `t.me/<bot>?start=<code>` within an hour. `/stop` in Telegram deletes every subscription linked to that chat.

## What it stores

One table holds the subscriptions: the account, channel, destination, threshold, whether it is confirmed, the state of each position's alerts, and the SHA-256 of the unsubscribe token. A second table holds the codes and tokens awaiting confirmation, as digests, and deletes them when used or expired. An unconfirmed subscription goes with its code. No message is kept.

## Channels

Email goes over SMTP with STARTTLS, which the server must offer before the password is sent. Telegram goes over the Bot API. Both carry plain text.

## Deploy on Railway

`alerts` deploys from `services/Dockerfile` with `SERVICE=alerts`. Set `TAPEHOUSE_ALERTS_LISTEN=[::]:8090`, `TAPEHOUSE_ALERTS_DB=/data/alerts.db`, `TAPEHOUSE_INDEXER_URL`, `TAPEHOUSE_ALERTS_URL` and `TAPEHOUSE_ALERTS_SECRET`, with the channel's variables. It keeps its subscriptions in SQLite, so it needs a volume at `/data`, `RAILWAY_RUN_UID=0` because Railway mounts volumes as root, and exactly one replica. It serves only `/v1/...` routes and has no health check path. To check it after a deployment, add its URL to `SERVICE_URLS` in `scripts/verify-deployment.sh` to confirm that it answers below 500.

## Security

The RPC URLs, the SMTP password, the bot token and the secret never appear in a log. Treat `TAPEHOUSE_ALERTS_SECRET` as a key: changing it invalidates every unsubscribe link already sent.

## Next

- [Run the indexer](https://docs.tapehouse.xyz/operate/indexer)
- [Liquidation](https://docs.tapehouse.xyz/concepts/liquidation)
- [Portfolio margin](https://docs.tapehouse.xyz/concepts/portfolio-margin)
