# scheduled-email

Auto-rotating secret generation — creates Argon2-hashed secrets and delivers
password-less login links via SMTP on a configurable schedule.

## Install

```bash
pip install scheduled-email
```

> 尚未发布到 PyPI 前，需先按 [PUBLISH.md](PUBLISH.md) 完成上传。
> 本地开发可用 editable 模式：`pip install -e packages/scheduled-email`

## Environment Variables

| Variable | Required | Default | Description |
|---|---|---|---|
| `SCHEDMAIL_SMTP_USER` | **yes** | — | Sender email address |
| `SCHEDMAIL_SMTP_PASS` | **yes** | — | SMTP authorization code |
| `SCHEDMAIL_RECIPIENT` | **yes** | — | Recipient email address |
| `SCHEDMAIL_BASE_URL` | no | `https://example.com/login?token=` | Prefix for login URL |
| `SCHEDMAIL_ROTATE_HOURS` | no | `24` | Rotation interval (hours) |
| `SCHEDMAIL_SCT` | no | `*****-daiju` | SCT constant |
| `SCHEDMAIL_MASK_SECRET` | no | `false` | Mask secret in email body |

Copy `.env.example` to `.env` and fill in credentials. **Never commit `.env`.**

## Usage

### Standalone

```python
from scheduled_email import start, stop

svc = start()       # immediate first rotation + email, then every 24h
# ...
stop()               # cancel timer
```

### Context Manager

```python
from scheduled_email import ScheduledEmailService

with ScheduledEmailService() as svc:
    print(svc.current_url)
```

### FastAPI Integration (minimal)

```python
from contextlib import asynccontextmanager
from fastapi import FastAPI

@asynccontextmanager
async def lifespan(app: FastAPI):
    from scheduled_email import start, stop
    start()
    yield
    stop()

app = FastAPI(lifespan=lifespan)
```

## How It Works

1. On `start()`, a 32-byte random secret is generated via `secrets.token_urlsafe`.
2. The secret is hashed with Argon2 (`argon2-cffi`).
3. A login URL is built: `{SCHEDMAIL_BASE_URL}{argon2_hash}`.
4. An email is sent via SMTP_SSL containing: rotation time, SCT, secret
   (optionally masked), and the full login URL.
5. A `threading.Timer` schedules the next rotation after `SCHEDMAIL_ROTATE_HOURS`.
