# Environment variables

> Every variable the CMS reads, which of them a .env file can reach, and the measured reason two of them cannot live there.

Source: https://editor.astrocraftthemes.com/docs/environment/

Nothing here is required to build. Every variable is optional, a fresh clone builds and runs with an empty environment, and the features that need one refuse with a readable message rather than failing the build.

## The two that go in `.env`

```ini
RESEND_API_KEY=
MAIL_FROM=
```

**`RESEND_API_KEY`** — from [Resend](https://resend.com) → API keys. Used by every action that sends mail.

**`MAIL_FROM`** — a **verified** sender on a domain you own in Resend. One mailbox for the whole product, not a sender per feature. Replies to an invite go to whoever sent it.

Both are read through `astro:env/server` rather than `import.meta.env`, because on Workers-style runtimes secrets exist only in the runtime environment. Both must be **declared** in `astro.config.mjs`'s `env.schema` even if you never set a value — see [Mounting](/docs/mounting/). The values are optional; the schema is structurally required, and without it the build dies with `[MISSING_EXPORT]`.

### What happens with them unset

Everything else works. The screens render, the guard guards, sign-in signs in, and the two mail paths refuse with a message on the page and a line in the server log.

Concretely: `/admin/team/` can still mint an invite and hand you the link to copy — it just cannot mail it. And `/admin/signup/` answers _"we can't create accounts right now, please try again later"_, which is **permanent** rather than transient, because confirming a mailed link is how an address is proved. Use the accounts CLI for the first account either way.

Resend is reached with a plain `fetch` — no SDK, ten-second timeout — and it never throws. Provider errors go to the server log only, because they can name the account, the sender domain and the mailbox.

## The two that cannot go in `.env`

```bash
export ASTROCRAFT_DB_URL=libsql://…turso.io
export ASTROCRAFT_DB_TOKEN=…
```

**These are read from `process.env`** — by `astro.config.mjs` at config load, and by the accounts CLI under plain Node. A `.env` file reaches **neither**: Vite loads it into `import.meta.env`, which is where the Resend pair is read from and nowhere these are.

This was verified rather than assumed: putting the URL in a `.env` and rebuilding left sessions on the filesystem and the CLI writing to `.accounts/`. It would look like it worked and silently do nothing, which is the one thing a configuration file must never do.

Export them in the shell, or use your host's environment UI. See [Bring a database](/docs/database/).

## The store's path is not a variable

There is no variable that says where `.accounts/` goes. The path is the literal string `.accounts`, resolved against **`process.cwd()`** — so it is decided by where you start the process, not by configuration.

That is worth knowing before you go looking for the setting: the way to move the store is to move the process's working directory, or to set `ASTROCRAFT_DB_URL` and stop having a path at all.

## The container's variables

These belong to the deployment rather than to the build, and go in your platform's environment UI:

| Variable              | Required               | What it is                                                   |
| :-------------------- | :--------------------- | :----------------------------------------------------------- |
| `GIT_URL`             | yes                    | the repository's HTTPS clone URL                             |
| `GIT_PUSH_TOKEN`      | one of these two       | a fine-grained PAT, _Contents: read and write_               |
| `GIT_SSH_COMMAND`     |                        | e.g. `ssh -i /run/secrets/deploy_key`, with an SSH `GIT_URL` |
| `GIT_COMMITTER_NAME`  | yes                    | the deployment's committer name                              |
| `GIT_COMMITTER_EMAIL` | yes                    | the deployment's committer address                           |
| `GIT_BRANCH`          | no (default `staging`) | the branch the checkout lives on                             |
| `PORT` / `HOST`       | no (4321 / 0.0.0.0)    | the standalone server                                        |

The push credential is passed to git through the environment, never through argv and never written into `.git/config`. Committer identity and an SSH deploy key cost no code at all — git reads both from the inherited environment.

## Your site's own build

**`PREVIEW=1`** on the staging branch's deploy, and nowhere else. It is the single flag that lets drafts render, and it also flips `robots.txt` to `Disallow: /` for that deploy.

**`BUILD_REV`** from `git rev-parse HEAD`, stamped into the page that answers `/`. The review screen polls it to confirm a publish went live.

## What is deliberately not an environment variable

The publish branch and the staging preview URL. Both are settings an admin sets on screen, stored with everything else. Restarting a deployment should not be how you change which branch you publish to.

## Troubleshooting

**The build dies with `[MISSING_EXPORT] "RESEND_API_KEY"`.** The `env.schema` block is missing from `astro.config.mjs`. This is about the schema, not the value.

**A database URL in `.env` did nothing.** Expected, and explained above. Export it in the shell instead.

**Mail sends without error but never arrives.** `MAIL_FROM` is not a verified sender on a domain you own in Resend. The provider's rejection is in the server log rather than on screen.

**A store URL that cannot be reached takes the whole CMS down.** Deliberately. It throws on the first store touch, naming both variables, and does **not** fall back to files — a deployment running against an empty store looks exactly like a fresh install, and a fresh install re-opens first-admin sign-up to whoever loads the page first.