Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ Edit `packages/backend/wrangler.toml` with the created resource values:
- `[vars].MAX_RECEIVED_EMAILS_PER_ADDRESS` (default: `100`)
- `[vars].MAX_INTEGRATIONS_PER_ORGANIZATION` (default: `3`)
- `[vars].MAX_INTEGRATION_DISPATCHES_PER_ORGANIZATION_PER_DAY` (default: `100`)
- `[vars].OPERATIONAL_EVENT_RETENTION_DAYS` (default: `30`)
- `[vars].OPERATIONAL_EVENT_MAX_METADATA_BYTES` (default: `4096`)
- `[vars].OPERATIONAL_EVENT_NOISY_RATE_LIMIT_WINDOW_SECONDS` and `[vars].OPERATIONAL_EVENT_NOISY_RATE_LIMIT_MAX` (default: `300` seconds and `1` stored event per noisy event identity)
- `[vars].API_KEY_RATE_LIMIT_WINDOW` and `[vars].API_KEY_RATE_LIMIT_MAX` (default: `60` seconds and `120` requests for `x-api-key` app traffic, including Better Auth runtime checks on `/get-session` and `/organization/get-full-organization`; these apply in addition to `AUTH_RATE_LIMIT_*` and `AUTH_CHANGE_EMAIL_RATE_LIMIT_*`)
- `[vars].AUTH_RATE_LIMIT_WINDOW` (default: `60`)
- `[vars].AUTH_RATE_LIMIT_MAX` (optional Better Auth global max override)
Expand Down Expand Up @@ -379,6 +382,19 @@ pnpm -C packages/backend db:migrate:dev
# for production, run `pnpm -C packages/backend db:migrate:prod`
```

### Bootstrap the first platform admin

Spinupmail does not auto-promote the first user and does not use env-based admin
IDs. After migrations have added the Better Auth admin fields, promote the first
admin directly in D1:

```bash
pnpm -C packages/backend exec wrangler d1 execute SUM_DB --local --command "UPDATE users SET role = 'admin' WHERE email = 'you@example.com';"
```

For production, run the same statement with `--remote` after confirming the
target user has signed up and verified their email.

## 5. Deploy the Backend Worker

```bash
Expand Down Expand Up @@ -620,6 +636,9 @@ Limits:
- `EMAIL_ATTACHMENTS_ENABLED`: when `false`, inbound attachments are ignored and attachment UI/API surfaces are disabled (`true` by default).
- `MAX_RECEIVED_EMAILS_PER_ORGANIZATION`: hard cap across all stored emails in one organization (default `1000`).
- `MAX_RECEIVED_EMAILS_PER_ADDRESS`: hard cap across stored emails in one address (default `100`).
- `OPERATIONAL_EVENT_RETENTION_DAYS`: operational event rows older than this are pruned by the scheduled Worker (default `30`).
- `OPERATIONAL_EVENT_MAX_METADATA_BYTES`: serialized metadata cap per operational event row (default `4096`).
- `OPERATIONAL_EVENT_NOISY_RATE_LIMIT_WINDOW_SECONDS` / `OPERATIONAL_EVENT_NOISY_RATE_LIMIT_MAX`: cap repeated low-value inbound operational events such as rejects, duplicates, limit hits, and abuse blocks (default `1` stored event per `300` seconds per normalized event identity).
- `EMAIL_STORE_HEADERS_IN_DB`: persist full header JSON in D1 (`false` by default).
- `EMAIL_STORE_RAW_IN_DB`: persist full raw MIME in D1 (`false` by default).
- `EMAIL_STORE_RAW_IN_R2`: persist full raw MIME in private R2 (`false` by default).
Expand Down
28 changes: 28 additions & 0 deletions packages/backend/drizzle/0011_wet_sleeper.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
CREATE TABLE `operational_events` (
`id` text PRIMARY KEY NOT NULL,
`severity` text NOT NULL,
`type` text NOT NULL,
`organization_id` text,
`address_id` text,
`email_id` text,
`integration_id` text,
`dispatch_id` text,
`message` text NOT NULL,
`metadata_json` text,
`created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL,
FOREIGN KEY (`organization_id`) REFERENCES `organizations`(`id`) ON UPDATE no action ON DELETE set null,
FOREIGN KEY (`address_id`) REFERENCES `email_addresses`(`id`) ON UPDATE no action ON DELETE set null,
FOREIGN KEY (`email_id`) REFERENCES `emails`(`id`) ON UPDATE no action ON DELETE set null,
FOREIGN KEY (`integration_id`) REFERENCES `organization_integrations`(`id`) ON UPDATE no action ON DELETE set null,
FOREIGN KEY (`dispatch_id`) REFERENCES `integration_dispatches`(`id`) ON UPDATE no action ON DELETE set null
);
--> statement-breakpoint
CREATE INDEX `operational_events_created_idx` ON `operational_events` (`created_at`);--> statement-breakpoint
CREATE INDEX `operational_events_severity_created_idx` ON `operational_events` (`severity`,`created_at`);--> statement-breakpoint
CREATE INDEX `operational_events_type_created_idx` ON `operational_events` (`type`,`created_at`);--> statement-breakpoint
CREATE INDEX `operational_events_org_created_idx` ON `operational_events` (`organization_id`,`created_at`);--> statement-breakpoint
ALTER TABLE `sessions` ADD `impersonated_by` text;--> statement-breakpoint
ALTER TABLE `users` ADD `role` text;--> statement-breakpoint
ALTER TABLE `users` ADD `banned` integer DEFAULT false;--> statement-breakpoint
ALTER TABLE `users` ADD `ban_reason` text;--> statement-breakpoint
ALTER TABLE `users` ADD `ban_expires` integer;
9 changes: 9 additions & 0 deletions packages/backend/drizzle/0012_flaky_hercules.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
DROP INDEX `operational_events_created_idx`;--> statement-breakpoint
DROP INDEX `operational_events_severity_created_idx`;--> statement-breakpoint
DROP INDEX `operational_events_type_created_idx`;--> statement-breakpoint
DROP INDEX `operational_events_org_created_idx`;--> statement-breakpoint
CREATE INDEX `operational_events_org_severity_type_created_idx` ON `operational_events` (`organization_id`,`severity`,`type`,"created_at" desc);--> statement-breakpoint
CREATE INDEX `operational_events_created_idx` ON `operational_events` ("created_at" desc);--> statement-breakpoint
CREATE INDEX `operational_events_severity_created_idx` ON `operational_events` (`severity`,"created_at" desc);--> statement-breakpoint
CREATE INDEX `operational_events_type_created_idx` ON `operational_events` (`type`,"created_at" desc);--> statement-breakpoint
CREATE INDEX `operational_events_org_created_idx` ON `operational_events` (`organization_id`,"created_at" desc);
Loading
Loading