wrangler.jsonc
Rama main · réplica de lectura del repositorio original.
/**
* For more details on how to configure Wrangler, refer to:
* https://developers.cloudflare.com/workers/wrangler/configuration/
*/
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "reccado",
"compatibility_date": "2026-06-30",
"compatibility_flags": ["nodejs_compat"],
"main": "src/server.ts",
"observability": {
"enabled": true
},
"upload_source_maps": true,
// Production should not be reachable on the shared *.workers.dev subdomain.
// Front public installs with a custom domain and Cloudflare Access.
"workers_dev": false,
"vars": {
"MAIL_FROM_ADDRESS": "noreply@mail.example.com",
// Mailbox Durable Objects are pinned to Cloudflare's EU jurisdiction.
// Declared rather than assumed because workerd does not implement
// jurisdictions, so `.dev.vars` overrides this to "none" locally and the
// guarantee only exists in a deployed Worker. See src/lib/mailbox-stub.ts.
"MAILBOX_JURISDICTION": "eu"
},
"triggers": {
"crons": ["0 * * * *"]
},
"send_email": [
{
"name": "EMAIL"
}
],
"durable_objects": {
"bindings": [
{
"name": "MAILBOX_DO",
"class_name": "MailboxDurableObject"
}
]
},
"r2_buckets": [
{
"binding": "MAIL_OBJECTS",
"bucket_name": "inbox-mcp-raw",
// Raw MIME is the actual message content at rest — more so than D1,
// which only holds snippets — so the residency story is incomplete
// without it. A fork that does not want EU residency changes this and
// MAILBOX_JURISDICTION together; changing one alone leaves a half
// guarantee, which is worse than none because it reads as done.
"jurisdiction": "eu"
}
],
"queues": {
"producers": [
{
"binding": "INBOUND_EMAIL_QUEUE",
"queue": "inbox-mcp-inbound"
},
{
"binding": "EMAIL_EVENTS_QUEUE",
"queue": "inbox-mcp-email-events"
},
{
"binding": "NOTIFY_QUEUE",
"queue": "inbox-mcp-notify"
}
],
"consumers": [
{
"queue": "inbox-mcp-inbound",
"dead_letter_queue": "inbox-mcp-inbound-dlq",
"max_batch_size": 5,
"max_batch_timeout": 2,
"max_retries": 3
},
{
"queue": "inbox-mcp-email-events",
"dead_letter_queue": "inbox-mcp-email-events-dlq",
"max_batch_size": 10,
"max_batch_timeout": 2,
"max_retries": 3
},
{
"queue": "inbox-mcp-notify",
"dead_letter_queue": "inbox-mcp-notify-dlq",
// Telegram takes about one message per second per chat, so a large
// batch would spend most of its invocation being rate-limited; five
// keeps a burst moving without stalling the consumer. The 1s timeout
// is the point of the queue — a notification that waits to fill a
// batch is a notification that arrived late. max_retries is higher
// than ingest's because the failures here are Telegram outages and
// 429s, which pass, and a dropped push is a signal the operator
// never gets back.
"max_batch_size": 5,
"max_batch_timeout": 1,
"max_retries": 5
},
// The three dead-letter queues are consumed by the same terminal
// tombstone handler: a dead message must land somewhere durable and
// named instead of evaporating when Queues retention expires. These
// entries deliberately declare no dead_letter_queue of their own —
// this is the end of the line, and a DLQ for the DLQ would only move
// the silence one hop away. Batches are large and unhurried because
// nothing downstream is waiting; the retries exist only to ride out a
// D1 blip while writing the record.
{
"queue": "inbox-mcp-inbound-dlq",
"max_batch_size": 10,
"max_batch_timeout": 5,
"max_retries": 5
},
{
"queue": "inbox-mcp-email-events-dlq",
"max_batch_size": 10,
"max_batch_timeout": 5,
"max_retries": 5
},
{
"queue": "inbox-mcp-notify-dlq",
"max_batch_size": 10,
"max_batch_timeout": 5,
"max_retries": 5
}
]
},
"d1_databases": [
{
"binding": "INDEX_DB",
"database_name": "inbox-mcp-index",
// Forkers: create your own D1 database (`wrangler d1 create
// inbox-mcp-index`) and paste its id here. This is a template
// value, not a real database.
"database_id": "<your-prod-d1-database-id>",
"migrations_dir": "migrations/d1"
}
],
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["MailboxDurableObject"]
}
],
"env": {
"dev": {
"name": "reccado-dev",
// Off, like production. Not for secrecy -- the perimeter is in the worker
// now, so both hostnames were equally protected -- but because the
// deployment learns its canonical origin from whichever host an
// authenticated request last arrived on, and that origin is what MCP
// tokens are bound to. A stale workers.dev tab loading one mailbox was
// enough to silently re-point it away from the custom domain.
"workers_dev": false,
// Who may drive the bot is still a trust declaration a human makes — but a
// personal Telegram user id committed to a public repository, changeable
// only by redeploy, was a bad way to write one down. The declaration moved
// to owner_identities in D1, made by sending `/start <pairing code>`.
// TELEGRAM_ALLOWED_USER_IDS survives as an emergency bootstrap and is
// deliberately not set here.
"vars": {
"MAIL_FROM_ADDRESS": "noreply@send.imsanti.dev",
// imsanti.dev is the zone apex, so hello@imsanti.dev sends as itself. Ship
// this only after `wrangler email sending list` shows imsanti.dev enabled:
// a domain listed here that Cloudflare has not accepted makes every send from
// that mailbox fail instead of falling back to MAIL_FROM_ADDRESS.
"MAIL_SENDING_DOMAINS": "imsanti.dev,send.transcribo.es,transcribo.es",
"MAILBOX_JURISDICTION": "eu"
},
"triggers": {
"crons": ["0 * * * *"]
},
"send_email": [
{
"name": "EMAIL"
}
],
"durable_objects": {
"bindings": [
{
"name": "MAILBOX_DO",
"class_name": "MailboxDurableObject"
}
]
},
"r2_buckets": [
{
"binding": "MAIL_OBJECTS",
"bucket_name": "inbox-mcp-raw-dev-eu",
"jurisdiction": "eu"
}
],
"queues": {
"producers": [
{
"binding": "INBOUND_EMAIL_QUEUE",
"queue": "inbox-mcp-inbound-dev"
},
{
"binding": "EMAIL_EVENTS_QUEUE",
"queue": "inbox-mcp-email-events-dev"
},
{
"binding": "NOTIFY_QUEUE",
"queue": "inbox-mcp-notify-dev"
}
],
"consumers": [
{
"queue": "inbox-mcp-inbound-dev",
"dead_letter_queue": "inbox-mcp-inbound-dlq-dev",
"max_batch_size": 5,
"max_batch_timeout": 2,
"max_retries": 3
},
{
"queue": "inbox-mcp-email-events-dev",
"dead_letter_queue": "inbox-mcp-email-events-dlq-dev",
"max_batch_size": 10,
"max_batch_timeout": 2,
"max_retries": 3
},
{
"queue": "inbox-mcp-notify-dev",
"dead_letter_queue": "inbox-mcp-notify-dlq-dev",
// Mirrors prod: small batches because Telegram is ~1 msg/s per
// chat, a 1s timeout because a batched-up notification is a late
// one, and more retries than ingest because 429s and outages pass
// while a dropped push never comes back.
"max_batch_size": 5,
"max_batch_timeout": 1,
"max_retries": 5
},
// Terminal tombstone consumers, as in prod: a dead message gets a
// durable, named row in ops_events rather than expiring unseen.
// No dead_letter_queue on purpose — the line ends here.
{
"queue": "inbox-mcp-inbound-dlq-dev",
"max_batch_size": 10,
"max_batch_timeout": 5,
"max_retries": 5
},
{
"queue": "inbox-mcp-email-events-dlq-dev",
"max_batch_size": 10,
"max_batch_timeout": 5,
"max_retries": 5
},
{
"queue": "inbox-mcp-notify-dlq-dev",
"max_batch_size": 10,
"max_batch_timeout": 5,
"max_retries": 5
}
]
},
"d1_databases": [
{
"binding": "INDEX_DB",
// Created with `--jurisdiction eu`, which is a property of the
// database rather than of this binding, so there is nothing here to
// declare and nothing to keep in sync. It is named `-eu` precisely
// because that fact is otherwise invisible from the config.
"database_name": "inbox-mcp-index-dev-eu",
// Forkers: create your own dev D1 (`wrangler d1 create <name>
// --update-config --binding INDEX_DB --env dev`) and this id gets
// filled in for you. The all-zero UUID is a valid-format placeholder
// (kept parseable because local `vitest` uses `environment: "dev"`);
// remote dev deploys still need a real id.
"database_id": "ac9f17a6-3159-4fd5-8f81-cd839d6aa043",
"migrations_dir": "migrations/d1"
}
],
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["MailboxDurableObject"]
}
]
}
}
}