{"id":35434189,"url":"https://github.com/systemli/userli-mailbox-janitor","last_synced_at":"2026-01-18T09:17:49.440Z","repository":{"id":323242255,"uuid":"1092602214","full_name":"systemli/userli-mailbox-janitor","owner":"systemli","description":null,"archived":false,"fork":false,"pushed_at":"2025-12-09T14:41:37.000Z","size":58,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-09T06:51:07.241Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/systemli.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-08T23:43:25.000Z","updated_at":"2025-12-09T14:41:40.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/systemli/userli-mailbox-janitor","commit_stats":null,"previous_names":["systemli/userli-mailbox-janitor"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/systemli/userli-mailbox-janitor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemli%2Fuserli-mailbox-janitor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemli%2Fuserli-mailbox-janitor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemli%2Fuserli-mailbox-janitor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemli%2Fuserli-mailbox-janitor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/systemli","download_url":"https://codeload.github.com/systemli/userli-mailbox-janitor/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemli%2Fuserli-mailbox-janitor/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28337737,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T06:09:07.588Z","status":"ssl_error","status_checked_at":"2026-01-12T06:05:18.301Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-02T22:01:32.537Z","updated_at":"2026-01-18T09:17:49.429Z","avatar_url":"https://github.com/systemli.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# userli-mailbox-janitor\n\n[![Integration](https://github.com/systemli/userli-mailbox-janitor/actions/workflows/integration.yml/badge.svg)](https://github.com/systemli/userli-mailbox-janitor/actions/workflows/integration.yml)\n[![Quality](https://github.com/systemli/userli-mailbox-janitor/actions/workflows/quality.yml/badge.svg)](https://github.com/systemli/userli-mailbox-janitor/actions/workflows/quality.yml)\n\nA webhook-based daemon that automatically purges deleted user mailboxes after a retention period.\n\n## Features\n\n- Listens for user deletion webhooks from userli\n- Stores mailbox deletion tasks in a simple CSV file (easy to edit manually)\n- Automatically purges mailboxes using a configurable command after configured retention period (default: 24h)\n- Updates last login time for inactive users who have sieve scripts with unconditional redirect rules\n- HMAC SHA256 webhook signature verification\n- Background worker with ticker for processing tasks\n- Structured logging with zap\n- Configurable via environment variables\n\n## How it works\n\n### Mailbox Purging\n1. **Webhook Reception**: Receives `user.deleted` events via HTTP POST to `/userli`\n2. **CSV Storage**: Stores the email and creation timestamp in a CSV file\n3. **Background Processing**: A ticker runs periodically (configurable interval) to check for due mailboxes\n4. **Mailbox Purging**: Executes the configured `PURGER_COMMAND` with placeholders replaced for each due mailbox\n5. **Cleanup**: Removes successfully purged mailboxes from the CSV file\n\n### Inactive User Retention (Toucher)\n1. **Fetch Inactive Users**: Periodically queries the Userli API for users marked as inactive\n2. **Sieve Script Analysis**: For each inactive user, reads their sieve configuration file\n3. **Redirect Detection**: Checks if the sieve script contains an unconditional redirect rule (`if true { redirect ... }`)\n4. **Touch User**: If a redirect rule is found, updates the user's last login timestamp via the Userli API\n5. **Retention Extension**: This prevents the user from being marked for deletion, allowing mail forwarding to continue\n\n## Installation\n\n### From Source\n\n```bash\ngit clone https://github.com/systemli/userli-mailbox-janitor.git\ncd userli-mailbox-janitor\ngo build -o userli-mailbox-janitor\n```\n\n## Configuration\n\nConfiguration is done via environment variables:\n\n| Variable                 | Description                                                                           | Default                                                                         |\n|--------------------------|---------------------------------------------------------------------------------------|---------------------------------------------------------------------------------|\n| `LOG_LEVEL`              | Logging level (debug, info, warn, error)                                              | `info`                                                                          |\n| `LISTEN_ADDR`            | HTTP server listen address                                                            | `:8080`                                                                         |\n| `WEBHOOK_SECRET`         | Secret for HMAC SHA256 signature verification                                         | *required*                                                                      |\n| `PURGER_DATABASE_PATH`   | Path to CSV file for storing mailbox data                                             | `./mailboxes.csv`                                                               |\n| `PURGER_RETENTION_HOURS` | Hours to wait before purging mailbox                                                  | `24`                                                                            |\n| `PURGER_TICK_INTERVAL`   | Interval for checking due mailboxes (e.g., \"5m\", \"1h\")                                | `1h`                                                                            |\n| `PURGER_COMMAND`         | Command to purge mailbox, with placeholders `{email}`, `{domain}`, and `{local_part}` | `echo 'No PURGER_COMMAND configured; skipping purge for {domain}/{local_part}'` |\n| `TOUCHER_TICK_INTERVAL`  | Interval for checking inactive users for retention (e.g., \"1h\", \"6h\")                 | `24h`                                                                           |\n| `TOUCHER_SIEVE_LOCATION` | Path pattern for sieve files with placeholders `{domain}` and `{local_part}`          | *required*                                                                      |\n| `TOUCHER_USE_SUDO`       | Whether to use sudo when accessing sieve files (true/false)                           | `false`                                                                         |\n| `USERLI_URL`             | Base URL of the Userli API                                                            | *required*                                                                      |\n| `USERLI_TOKEN`           | API token for Userli authentication                                                   | *required*                                                                      |\n\n## Usage\n\n### Running the Service\n\n```bash\nexport WEBHOOK_SECRET=\"your-secret-here\"\nexport PURGER_DATABASE_PATH=\"/var/lib/mailbox-janitor/mailboxes.csv\"\nexport TOUCHER_SIEVE_LOCATION=\"/var/vmail/{domain}/{local_part}/.dovecot.sieve\"\nexport USERLI_URL=\"https://userli.example.org\"\nexport USERLI_TOKEN=\"your-api-token-here\"\n./userli-mailbox-janitor\n```\n\n### Webhook Integration\n\nConfigure userli to send webhooks to your janitor instance:\n\n```bash\nWEBHOOK_URL=\"https://mailbox-janitor.example.org/userli\"\nSECRET=\"your-secret-here\"\nPAYLOAD='{\"type\":\"user.deleted\",\"timestamp\":\"2025-01-01T00:00:00.000000Z\",\"data\":{\"email\":\"user@example.org\"}}'\nSIGNATURE=$(printf '%s' \"$PAYLOAD\" | openssl dgst -sha256 -hmac \"$SECRET\" | sed 's/^.* //')\n\ncurl -i \"$WEBHOOK_URL\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"X-Webhook-Signature: $SIGNATURE\" \\\n  -d \"$PAYLOAD\"\n```\n\n### Userli API Integration\n\nThe Toucher feature requires access to the following Userli API endpoints:\n\n- `GET /api/retention/users` - Fetch list of inactive users\n- `PUT /api/retention/{email}/touch` - Update user's last login timestamp\n\n## Sieve Redirect Detection\n\nThe Toucher feature analyzes sieve scripts to identify users who have configured unconditional email forwarding. This is useful for maintaining active forwarding for users who may appear inactive but are still using the service to redirect their emails.\n\n### Detection Logic\n\nThe system looks for sieve scripts containing the following pattern:\n\n```sieve\nif true {\n  redirect \"destination@exampla.com\";\n}\n```\n### Example Sieve Configurations\n\n**Will trigger touch (unconditional redirect):**\n```sieve\n# User has configured forwarding\nif true {\n  redirect \"new-email@example.com\";\n}\n```\n\n**Will NOT trigger touch (conditional redirect):**\n```sieve\n# Spam filtering with conditional redirect\nif header :contains \"X-Spam-Flag\" \"YES\" {\n  redirect \"spam-report@example.com\";\n}\n```\n\n## Development\n\n### Running Tests\n\n```bash\ngo test -v ./...\n```\n\n### Test Coverage\n\n```bash\ngo test -coverprofile=coverage.out ./...\ngo tool cover -html=coverage.out\n```\n\n### Linting\n\n```bash\ngolangci-lint run\n```\n\n## License\n\nThis project is licensed under the GNU Affero General Public License v3.0 - see the [LICENSE](LICENSE) file for details.\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## Support\n\nFor issues and questions, please use the [GitHub issue tracker](https://github.com/systemli/userli-mailbox-janitor/issues).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsystemli%2Fuserli-mailbox-janitor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsystemli%2Fuserli-mailbox-janitor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsystemli%2Fuserli-mailbox-janitor/lists"}