{"owner":"haraka","repo":"Haraka","hasSkills":true,"totalSkillsCount":34,"totalTokensCount":14341,"categories":["copilot-instructions","root-instruction","claude-rule","anthropic-skill"],"hasMcp":false,"mcpConfig":null,"found":[".github/copilot-instructions.md","AGENTS.md","CLAUDE.md","docs/plugins/aliases.md","docs/plugins/auth/auth_bridge.md","docs/plugins/auth/auth_ldap.md","docs/plugins/auth/auth_proxy.md","docs/plugins/auth/auth_vpopmaild.md","docs/plugins/auth/flat_file.md","docs/plugins/block_me.md","docs/plugins/data.signatures.md","docs/plugins/delay_deny.md","docs/plugins/max_unrecognized_commands.md","docs/plugins/prevent_credential_leaks.md","docs/plugins/process_title.md","docs/plugins/queue/deliver.md","docs/plugins/queue/discard.md","docs/plugins/queue/lmtp.md","docs/plugins/queue/qmail-queue.md","docs/plugins/queue/quarantine.md","docs/plugins/queue/smtp_bridge.md","docs/plugins/queue/smtp_forward.md","docs/plugins/queue/smtp_proxy.md","docs/plugins/queue/test.md","docs/plugins/rcpt_to.in_host_list.md","docs/plugins/rcpt_to.max_count.md","docs/plugins/record_envelope_addresses.md","docs/plugins/relay.md","docs/plugins/reseed_rng.md","docs/plugins/status.md","docs/plugins/tarpit.md","docs/plugins/tls.md","docs/plugins/toobusy.md","docs/plugins/xclient.md"],"skills":{".github/copilot-instructions.md":"<!-- agents-version: 3 -->\n\n# AGENTS.md\n\nShared instructions for every coding agent in this repo. The tool-specific files\n(`CLAUDE.md`, `GEMINI.md`, `.github/copilot-instructions.md`) defer to this one.\nIf a package-level instruction file exists, it is authoritative for that package.\n\n## Repository model\n\n- This file is shared across every Haraka repository. It describes the whole family — not just this repo. Each repo is an independent npm package, there is no root workspace. Run every command from the package root.\n- Repos are worked on both standalone and as sibling checkouts in a combined tree (`Haraka/`, `plugin/<name>/`, …). Don't assume sibling packages are present on disk.\n- The family:\n  - `Haraka` — core SMTP server (has `./run_tests`).\n  - `haraka-plugin-<name>` — optional plugins; may depend on each other (e.g. bounce → spf).\n  - `haraka-config` — config loader with hot-reload.\n  - `haraka-results`, `haraka-notes` — per-connection result / note tracking.\n  - `haraka-net-utils`, `haraka-utils`, `haraka-constants`, `haraka-dsn`, `haraka-tld`, `haraka-message-stream` — shared libraries.\n  - `@haraka/email-address` — the RFC 5321/5322 parser (supersedes the deprecated `address-rfc282x`).\n  - `@haraka/eslint-config`, `haraka-test-fixtures`.\n\n## Working agreement\n\n- Do only what was asked. When you spot an adjacent bug or smell, surface it and ask before expanding scope. Don't silently refactor, and don't ignore it.\n- Preserve compatibility; break it only for an explicit, stated reason.\n- For protocol behavior, identify the relevant RFC and verify conformance against the existing implementation.\n\n## Source control\n\n- Never run history- or remote-mutating commands (`git commit`, `git push`, `git tag`, `gh pr create`) unless explicitly asked.\n- create diffs; the human reviews, commits, and pushes.\n- No trailers on commit messages or PR bodies.\n- When asked to generate a PR:\n  - keep it succinct, terse, and DRY\n  - Post a link to it in the console\n- Document changes relative to main in CHANGELOG.md\n  - one clear concise entry per change, < 50 chars\n  - prefer Conventional Commit format, imperative mood.\n  - Rationale belongs in the code or PR, not the bullet.\n\n## Coding standards\n\n- Target current Node LTS; prefer ES2024 over legacy patterns.\n- Existing code is CommonJS (`require`/`exports`) — match it. New modules should use ESM with CJS interop (see `@haraka/email-address`).\n- Add `node:` prefixes to built-in requires in any file you touch (`require('fs')` → `require('node:fs')`).\n- Prefer: promise APIs (`fs/promises`), `for...of`/`for...in` over `forEach`, `node:readline` for line parsing, template literals over concatenation, `true`/`false` over `1`/`0`, and guard-style early returns.\n- Remove commented-out code (it lives in git history). `npm run qlty` must pass without warnings.\n\n## Concision\n\n- Don't Repeat Yourself: Say each thing exactly once, in the place it belongs.\n- Sentences: must carry non-obvious facts the reader cannot get from the surroundings.\n- Cut scaffolding, keep facts.\n\nNever write these:\n\n- **Announcements.** \"One question:\", \"Two reasons:\", \"The point is:\", \"Here's the thing:\". Say the thing. If you announce a count, the count must be right — so don't announce one.\n- **Justification tails.** A clause after a comma or dash that argues for the clause before it: \"…and a guess there is worse than none\", \"…which is the failure this exists to prevent\".\n- **The contrast tic.** \"X, not Y\" and \"X rather than Y\" where Y is obvious. State X.\n- **Clauses doing an adjective's job.** \"a setting nobody has written\" is \"an unwritten setting\"; \"a copy nobody has opened\" is \"an unopened copy\"; \"still has to\" is \"must\". A state with a name takes the name.\n- **Closing morals.** A final sentence restating the paragraph's point.\n\n## Comments\n\n- Keep only WHY comments — a hidden constraint, an invariant, a workaround for a specific bug, or an RFC citation that explains otherwise-surprising behavior.\n- Delete WHAT comments that restate the code.\n- When a rename makes a comment redundant, delete it.\n- Never narrate history or audit findings.\n- Never count the code — counts rot.\n- Prefer self-documenting code: a better name beats any comment.\n\n## Haraka plugins\n\n- Full hook/API reference: `docs/Plugins.md` in the `Haraka` core repo.\n- A plugin is an npm package: `index.js` (`exports.register` + hook handlers), `config/` (default `.ini`/`.json`/`.yaml`), `test/`, `README.md`.\n- Register hooks in `exports.register` with `this.register_hook('phase', 'method'[, priority])`.\n- Hook handlers take `(next, connection)` (rcpt hooks also take `rcpt`) and must call `next` exactly once. Gate early — return `next()` on missing transaction, disabled config, or skip conditions. Signal a verdict with `next(DENY|DENYSOFT|OK, msg)`; `DENY`/`OK`/etc. are plugin-scope globals (no import).\n- Results: `connection.transaction.results.add(this, { pass|fail|skip|msg|err, emit })`; query with `results.has(plugin, list, search)`. `emit: true` already logs the collated line — don't also `loginfo`/`logerror` the same thing. results.add(this, {err}) always logs.\n- Config loads via `config.get` with a hot-reload callback; declare every boolean or it stays a string and `=== true/false` silently fails:\n  ```js\n  this.cfg = this.config.get('name.ini', { booleans: ['+a.b', '-c.d'] }, () => this.load())\n  ```\n- Keep handlers thin. Push pure decision logic and I/O into `lib/*.js` as pure functions that return a verdict/value; the handler just maps that to `results.add` + `next`. For external I/O (DNS, network), expose an injectable seam — a swappable function whose default is the real implementation — so tests run without mocks.\n- If you add files outside `index.js` (e.g. a `lib/` dir), add them to `package.json` `files` so they publish.\n\n## Testing\n\n- Test real behavior and observable outcomes — `results`, return codes, emitted headers, side effects — not how a function was called. Asserting call shape (`calledWith`, arity, call counts) tests the test and hides signature drift.\n- Mocks/stubs are a smell. Prefer real inputs; when you must isolate a dependency, inject a seam and assert the outcome. Never leave a stub that neuters the path under test — that yields green tests proving nothing.\n- For bug fixes, add a failing test first, then fix.\n- Every feature ships with meaningful tests. A `.skip` is a coverage hole: fix it or delete it.\n- Use `node:test` and `node:assert/strict` for new tests and Mocha migrations. Plugin tests use `haraka-test-fixtures` (`makePlugin`, `makeConnection`, `callHook`).\n- Run the package's `lint`, `prettier`, and `format` before handoff.\n\n## Commands (run inside the target package)\n\n- Test: `npm test`. Single file: `node --test test/path/to/file.js`.\n- Haraka core repo only: `./run_tests [test/plugins/foo.js]`.\n- Coverage: `npm run test:coverage`; lcov: `npm run test:coverage:lcov`. Keep coverage at/above ~90%.\n- If coverage output includes non-source files (e.g. `package.json`, `test/*`), scope it with `--test-coverage-include` (preferred when the list is short) or `--test-coverage-exclude`.\n- Lint/format: `npm run lint` / `prettier` / `format`. Version drift: `npm run versions[:fix]`.\n\n## Package script parity\n\n- node:test packages should expose `test`, `test:coverage`, `test:coverage:lcov`, `lint`, `prettier`, `format` with matching shapes across siblings. Standardize on node:test coverage (not c8); add the canonical scripts when touching a package that lacks them:\n  ```jsonc\n  \"test:coverage\": \"node --test --experimental-test-coverage\",\n  \"test:coverage:lcov\": \"mkdir -p coverage && node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info\"\n  ```\n\n## Repo badges\n\n- Code climate is deprecated. Update with qlty.sh instead.\n- The canonical format for badges should be:\n  - Top of README.md:\n    - [![Test][ci-img]][ci-url] [![Cover][cov-img]][cov-url] [![Qlty][qlty-img]][qlty-url]\n  - Bottom of README.md:\n    - [ci-img]: https://github.com/haraka/<name>/actions/workflows/ci.yml/badge.svg\n    - [ci-url]: https://github.com/haraka/<name>/actions/workflows/ci.yml\n    - [cov-img]: https://codecov.io/github/haraka/<name>/coverage.svg\n    - [cov-url]: https://codecov.io/github/haraka/<name>\n    - [qlty-img]: https://qlty.sh/gh/haraka/projects/<name>/maintainability.svg\n    - [qlty-url]: https://qlty.sh/gh/haraka/projects/<name>\n","AGENTS.md":"<!-- agents-version: 3 -->\n\n# AGENTS.md\n\nShared instructions for every coding agent in this repo. The tool-specific files\n(`CLAUDE.md`, `GEMINI.md`, `.github/copilot-instructions.md`) defer to this one.\nIf a package-level instruction file exists, it is authoritative for that package.\n\n## Repository model\n\n- This file is shared across every Haraka repository. It describes the whole family — not just this repo. Each repo is an independent npm package, there is no root workspace. Run every command from the package root.\n- Repos are worked on both standalone and as sibling checkouts in a combined tree (`Haraka/`, `plugin/<name>/`, …). Don't assume sibling packages are present on disk.\n- The family:\n  - `Haraka` — core SMTP server (has `./run_tests`).\n  - `haraka-plugin-<name>` — optional plugins; may depend on each other (e.g. bounce → spf).\n  - `haraka-config` — config loader with hot-reload.\n  - `haraka-results`, `haraka-notes` — per-connection result / note tracking.\n  - `haraka-net-utils`, `haraka-utils`, `haraka-constants`, `haraka-dsn`, `haraka-tld`, `haraka-message-stream` — shared libraries.\n  - `@haraka/email-address` — the RFC 5321/5322 parser (supersedes the deprecated `address-rfc282x`).\n  - `@haraka/eslint-config`, `haraka-test-fixtures`.\n\n## Working agreement\n\n- Do only what was asked. When you spot an adjacent bug or smell, surface it and ask before expanding scope. Don't silently refactor, and don't ignore it.\n- Preserve compatibility; break it only for an explicit, stated reason.\n- For protocol behavior, identify the relevant RFC and verify conformance against the existing implementation.\n\n## Source control\n\n- Never run history- or remote-mutating commands (`git commit`, `git push`, `git tag`, `gh pr create`) unless explicitly asked.\n- create diffs; the human reviews, commits, and pushes.\n- No trailers on commit messages or PR bodies.\n- When asked to generate a PR:\n  - keep it succinct, terse, and DRY\n  - Post a link to it in the console\n- Document changes relative to main in CHANGELOG.md\n  - one clear concise entry per change, < 50 chars\n  - prefer Conventional Commit format, imperative mood.\n  - Rationale belongs in the code or PR, not the bullet.\n\n## Coding standards\n\n- Target current Node LTS; prefer ES2024 over legacy patterns.\n- Existing code is CommonJS (`require`/`exports`) — match it. New modules should use ESM with CJS interop (see `@haraka/email-address`).\n- Add `node:` prefixes to built-in requires in any file you touch (`require('fs')` → `require('node:fs')`).\n- Prefer: promise APIs (`fs/promises`), `for...of`/`for...in` over `forEach`, `node:readline` for line parsing, template literals over concatenation, `true`/`false` over `1`/`0`, and guard-style early returns.\n- Remove commented-out code (it lives in git history). `npm run qlty` must pass without warnings.\n\n## Concision\n\n- Don't Repeat Yourself: Say each thing exactly once, in the place it belongs.\n- Sentences: must carry non-obvious facts the reader cannot get from the surroundings.\n- Cut scaffolding, keep facts.\n\nNever write these:\n\n- **Announcements.** \"One question:\", \"Two reasons:\", \"The point is:\", \"Here's the thing:\". Say the thing. If you announce a count, the count must be right — so don't announce one.\n- **Justification tails.** A clause after a comma or dash that argues for the clause before it: \"…and a guess there is worse than none\", \"…which is the failure this exists to prevent\".\n- **The contrast tic.** \"X, not Y\" and \"X rather than Y\" where Y is obvious. State X.\n- **Clauses doing an adjective's job.** \"a setting nobody has written\" is \"an unwritten setting\"; \"a copy nobody has opened\" is \"an unopened copy\"; \"still has to\" is \"must\". A state with a name takes the name.\n- **Closing morals.** A final sentence restating the paragraph's point.\n\n## Comments\n\n- Keep only WHY comments — a hidden constraint, an invariant, a workaround for a specific bug, or an RFC citation that explains otherwise-surprising behavior.\n- Delete WHAT comments that restate the code.\n- When a rename makes a comment redundant, delete it.\n- Never narrate history or audit findings.\n- Never count the code — counts rot.\n- Prefer self-documenting code: a better name beats any comment.\n\n## Haraka plugins\n\n- Full hook/API reference: `docs/Plugins.md` in the `Haraka` core repo.\n- A plugin is an npm package: `index.js` (`exports.register` + hook handlers), `config/` (default `.ini`/`.json`/`.yaml`), `test/`, `README.md`.\n- Register hooks in `exports.register` with `this.register_hook('phase', 'method'[, priority])`.\n- Hook handlers take `(next, connection)` (rcpt hooks also take `rcpt`) and must call `next` exactly once. Gate early — return `next()` on missing transaction, disabled config, or skip conditions. Signal a verdict with `next(DENY|DENYSOFT|OK, msg)`; `DENY`/`OK`/etc. are plugin-scope globals (no import).\n- Results: `connection.transaction.results.add(this, { pass|fail|skip|msg|err, emit })`; query with `results.has(plugin, list, search)`. `emit: true` already logs the collated line — don't also `loginfo`/`logerror` the same thing. results.add(this, {err}) always logs.\n- Config loads via `config.get` with a hot-reload callback; declare every boolean or it stays a string and `=== true/false` silently fails:\n  ```js\n  this.cfg = this.config.get('name.ini', { booleans: ['+a.b', '-c.d'] }, () => this.load())\n  ```\n- Keep handlers thin. Push pure decision logic and I/O into `lib/*.js` as pure functions that return a verdict/value; the handler just maps that to `results.add` + `next`. For external I/O (DNS, network), expose an injectable seam — a swappable function whose default is the real implementation — so tests run without mocks.\n- If you add files outside `index.js` (e.g. a `lib/` dir), add them to `package.json` `files` so they publish.\n\n## Testing\n\n- Test real behavior and observable outcomes — `results`, return codes, emitted headers, side effects — not how a function was called. Asserting call shape (`calledWith`, arity, call counts) tests the test and hides signature drift.\n- Mocks/stubs are a smell. Prefer real inputs; when you must isolate a dependency, inject a seam and assert the outcome. Never leave a stub that neuters the path under test — that yields green tests proving nothing.\n- For bug fixes, add a failing test first, then fix.\n- Every feature ships with meaningful tests. A `.skip` is a coverage hole: fix it or delete it.\n- Use `node:test` and `node:assert/strict` for new tests and Mocha migrations. Plugin tests use `haraka-test-fixtures` (`makePlugin`, `makeConnection`, `callHook`).\n- Run the package's `lint`, `prettier`, and `format` before handoff.\n\n## Commands (run inside the target package)\n\n- Test: `npm test`. Single file: `node --test test/path/to/file.js`.\n- Haraka core repo only: `./run_tests [test/plugins/foo.js]`.\n- Coverage: `npm run test:coverage`; lcov: `npm run test:coverage:lcov`. Keep coverage at/above ~90%.\n- If coverage output includes non-source files (e.g. `package.json`, `test/*`), scope it with `--test-coverage-include` (preferred when the list is short) or `--test-coverage-exclude`.\n- Lint/format: `npm run lint` / `prettier` / `format`. Version drift: `npm run versions[:fix]`.\n\n## Package script parity\n\n- node:test packages should expose `test`, `test:coverage`, `test:coverage:lcov`, `lint`, `prettier`, `format` with matching shapes across siblings. Standardize on node:test coverage (not c8); add the canonical scripts when touching a package that lacks them:\n  ```jsonc\n  \"test:coverage\": \"node --test --experimental-test-coverage\",\n  \"test:coverage:lcov\": \"mkdir -p coverage && node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info\"\n  ```\n\n## Repo badges\n\n- Code climate is deprecated. Update with qlty.sh instead.\n- The canonical format for badges should be:\n  - Top of README.md:\n    - [![Test][ci-img]][ci-url] [![Cover][cov-img]][cov-url] [![Qlty][qlty-img]][qlty-url]\n  - Bottom of README.md:\n    - [ci-img]: https://github.com/haraka/<name>/actions/workflows/ci.yml/badge.svg\n    - [ci-url]: https://github.com/haraka/<name>/actions/workflows/ci.yml\n    - [cov-img]: https://codecov.io/github/haraka/<name>/coverage.svg\n    - [cov-url]: https://codecov.io/github/haraka/<name>\n    - [qlty-img]: https://qlty.sh/gh/haraka/projects/<name>/maintainability.svg\n    - [qlty-url]: https://qlty.sh/gh/haraka/projects/<name>\n","CLAUDE.md":"see AGENTS.md\n","docs/plugins/aliases.md":"# aliases\n\nRepackaged as [haraka-plugin-aliases](https://github.com/haraka/haraka-plugin-aliases).\n","docs/plugins/auth/auth_bridge.md":"# auth/auth_bridge\n\nThis plugin allows you to authenticate users to remote SMTP servers\nbridging the original user and password to the remote server,\nand proxy the result back to authenticate the client.\n\nThis plugin is meant to be used with the plugin `queue/smtp_bridge`.\n\nIt is different than `auth/auth_proxy` because it doesn't require\nthe AUTH user in user@domain.com format, and it doesn't check that\nthe domain is the configuration file. This plugins simply takes\nthe original user and password and tries to authenticate it in the\nremote SMTP server.\n\n## Configuration\n\nConfiguration is stored in `config/smtp_bridge.ini` and uses the INI\nstyle formatting.\n\nThe configuration of this plugin is simple:\n\n    host=localhost\n    #port=\n    #auth_type=\n    #priority=10\n\n- host: the host where you will be authenticating and posting,\n  for example `smtp.host.tld`, `192.0.2.25:587`, or `[2001:db8::1]:587`.\n  This is the only setting required. See [Endpoint][endpoint] for the\n  accepted forms. An IPv6 address must be bracketed to include a port.\n\nIf needed you can also set\n\n- port: used when `host` doesn't include one. Defaults to 25.\n\n[endpoint]: https://github.com/haraka/haraka-net-utils#endpoint\n\nThe options `auth_type` and `priority` will be used by `queue/smtp_bridge`\n","docs/plugins/auth/auth_ldap.md":"# auth/auth_ldap\n\nRepackaged as [haraka-plugin-auth-ldap](https://github.com/haraka/haraka-plugin-auth-ldap).\nLoading `auth/auth_ldap` in `config/plugins` is auto-redirected to `auth-ldap`.\n","docs/plugins/auth/auth_proxy.md":"# auth/auth_proxy\n\nThis plugin allows you to authenticate users by domain to remote SMTP servers\nand proxy the result back to authenticate the client.\n\nFor this to work - the AUTH username _must_ be in user@domain.com format\nregardless as to whether the remote SMTP server requires it in this format.\nThe domain part of the username is used to look-up which SMTP servers should\nbe used to authenticate users for that domain.\nWhen sending the AUTH credentials to the remote server, this plugin will try\nand send the full username e.g. user@domain.com first and if this fails it\nwill then strip the @domain.com part and just send the unqualified username.\n\nDue to the way this plugin works - it can only support PLAIN and LOGIN\nauthentication methods and for this reason it requires that STARTTLS be\nused via the tls plugin before it will advertise AUTH capabilities by the\nEHLO command. When connecting to the remote SMTP systems it will always\nattempt STARTTLS if it is offered, but it does _not_ require it, so caution\nshould be exercised.\n\n## Configuration\n\nConfiguration is stored in `config/auth_proxy.ini` and uses the INI\nstyle formatting.\n\nThe configuration of this plugin is simple:\n\n    [domains]\n    domain.com = server1.domain.com:587 server2.domain.com\n\nWhere domain.com is the domain-part of the username equals a list of hosts\nthat should be consulted in host:port format. The :port is optional and will\ndefault to 25. The list of hosts can be space, semi-colon or comma separated.\n\nIf more than host is specified, then subsequent hosts will only be tested if\nthere is some sort of error e.g. timeout, connection or protocol error.\n","docs/plugins/auth/auth_vpopmaild.md":"# auth/auth_vpopmaild\n\nThe `auth/vpopmaild` plugin allows SMTP users to authenticate against a vpopmaild daemon.\n\n## Configuration\n\nThe configuration file is stored in `config/auth_vpopmaild.ini`.\n\n### settings\n\n- host: The host/IP that vpopmaild is listening on (default: localhost).\n\n- port: The TCP port that vpopmaild is listening on (default: 89).\n\n- sysadmin: A colon separated username:password of a vpopmail user with SYSADMIN privileges (see vpopmail/bin/vmoduser -S). This is **only** necessary to support CRAM-MD5 which requires access to the clear text password. On new installs, it's best not to use CRAM-MD5, as it requires storing clear text passwords. Legacy clients with MUAs configured to authenticate with CRAM-MD5 will need this enabled.\n\n- constrain_sender: (default: true). For outbound messages (due to successful AUTH), constrain the envelope sender (MAIL FROM) to the same domain as the authenticated user. This setting, combined with `rate_rcpt_sender` in the [limit](https://github.com/haraka/haraka-plugin-limit) plugin can dramatically reduce the amount of backscatter and spam sent when an email account is compromised.\n\n### Per-domain Configuration\n\nAdditionally, domains can each have their own configuration for connecting\nto vpopmaild. The defaults are the same, so only the differences needs to\nbe declared. Example:\n\n```ini\n[example.com]\nhost=192.168.0.1\nport=999\n\n[example2.com]\nhost=192.168.0.2\nsysadmin=postmaster@example2.com:sekret\n```\n","docs/plugins/auth/flat_file.md":"# auth/flat_file\n\nThe `auth/flat_file` plugin allows you to create a file containing username and password combinations, and have relaying users authenticate from that file.\n\nNote that passwords are stored in clear-text, so this may not be a great idea for large scale systems. However the plugin would be a good start for someone looking to implement authentication using some other form of auth.\n\n**Security** - it is recommended to switch to [auth-encfile][url-authencflat] to protect your user credentials.\n\n**IMPORANT NOTE** - this plugin requires that STARTTLS be used via the tls plugin before it will advertise AUTH capabilities by the EHLO command. Localhost and IPs in RFC1918 ranges\nare exempt from this rule.\n\n## Configuration\n\nConfiguration is stored in `config/auth_flat_file.ini`.\n\n- [core]methods\n\nAuthentication methods are listed in the `[core]methods` parameter. Authentification methods are comma separated. Currently supported methods are: `CRAM-MD5`, `PLAIN` and `LOGIN`. The `PLAIN` and `LOGIN` methods are insecure and require TLS to be enabled.\n\n- [core]constrain_sender: (default: true). For outbound messages (due to successful AUTH), constrain the envelope sender (MAIL FROM) to the same domain as the authenticated user. This setting, combined with `rate_rcpt_sender` in the [limit](https://github.com/haraka/haraka-plugin-limit) plugin can dramatically reduce the amount of backscatter and spam sent when an email account is compromised.\n\nExample:\n\n```ini\n[core]\nmethods=PLAIN,LOGIN,CRAM-MD5\nconstrain_sender=true\n```\n\nUsers are stored in the `[users]` section.\n\nExample:\n\n```ini\n[users]\nuser1=password1\nuser@domain.com=password2\n```\n\n[url-authencflat]: https://github.com/AuspeXeu/haraka-plugin-auth-enc-file\n","docs/plugins/block_me.md":"# block_me\n\nThis plugin allows you to configure an address which mail sent to will be\nparsed for a From: address in the body of the message, and will add that\nfrom address to the `mail_from.blocklist` config file.\n\nEffectively this allows your users to forward spams that got through to a\nparticular mailbox to block them in the future.\n\nNote that this is a system-wide block, and not per-user. Be careful with this.\n\n## Configuration\n\n- `config/block_me.recipient` - a file containing the address to email to\n  get something blocked. For example: **spam@domain.com**.\n\n- `config/block_me.senders` - a file containing a list of email addresses\n  that are allowed to email the dropbox.\n","docs/plugins/data.signatures.md":"# data.signatures\n\nThis plugin allows you to add string signatures to a configuration file and\nhave this plugin scan the body text of an email for those strings. Mails\nmatching these signatures will be blocked.\n\n## Configuration\n\n- data.signatures\n\n  This file contains a list of strings (one per line) that will be matched.\n","docs/plugins/delay_deny.md":"# delay_deny\n\nDelays all pre-DATA 'deny' results until the recipients are sent\nand all post-DATA commands until all hook_data_post plugins have run.\nThis allows relays and authenticated users to bypass pre-DATA rejections.\n\n## Configuration\n\nConfiguration options are in config/delay_deny.ini.\n\nThis plugin operates in one of two modes: included and excluded.\n\n### included plugins\n\nA comma or semicolon separated list of denials that are to be included.\nIn this mode, _only_ plugins in the list are bypassed. All other plugins\ncan immediately reject connections.\n\n### excluded plugins\n\nA comma or semicolon separated list of denials that are to be excluded.\nExcluded plugins that are not bypassed and can still immediately reject\nconnections.\n","docs/plugins/max_unrecognized_commands.md":"# max_unrecognized_commands\n\nThe functionality of this plugin was folded into\n[haraka-plugin-limit](https://github.com/haraka/haraka-plugin-limit).\nLoading `max_unrecognized_commands` in `config/plugins` is auto-redirected\nto `limit`.\n","docs/plugins/prevent_credential_leaks.md":"# prevent_credential_leaks\n\nThis plugin prevents an authenticated user (via SMTP AUTH) from sending\ntheir username and password out in a message (e.g. like replying to a\nphish).\n\nIf their username and password are detected inside the message body, then\nthe message is rejected with the message:\n\n```\nCredential leak detected: never give out your username/password to anyone!\n```\n\nNote that if the username is qualified e.g. user@domain.com - then the\nplugin will search for both `user` and `user@domain.com` for maximum\neffectiveness.\n\n## Configuration\n\nNo configuration is required. Simply add the plugin to your `config/plugins`\nfile. It should be added before any other plugins that run on hook_data_post\nfor maximum efficiency.\n","docs/plugins/process_title.md":"# process_title\n\nThis plugin causes the process title seen by the UNIX 'ps' command to\nbe modified from this:\n\n```\nnode haraka.js -c /etc/haraka\n```\n\nto this:\n\n```\nHaraka (master) cn=11148 cc=1082 cps=21/25.24/79 rcpts=144950/1.84 rps=518/328.18/586 msgs=78815/7.07 mps=302/178.44/329 out=0/0/0 respawn=0\n \\_ Haraka (worker) cn=1646 cc=140 cps=5/3.73/17 rcpts=20310/1.86 rps=75/46.04/102 msgs=10938/6.65 mps=42/24.8/56 out=0/0/0\n \\_ Haraka (worker) cn=1563 cc=168 cps=3/3.54/18 rcpts=19844/1.87 rps=78/45/96 msgs=10627/6.8 mps=49/24.1/53 out=0/0/0\n \\_ Haraka (worker) cn=1852 cc=172 cps=3/4.2/16 rcpts=26278/2.03 rps=93/59.56/114 msgs=12938/6.99 mps=40/29.33/65 out=0/0/0\n \\_ Haraka (worker) cn=1704 cc=187 cps=5/3.86/14 rcpts=23688/1.84 rps=93/53.7/125 msgs=12886/7.56 mps=64/29.21/66 out=0/0/0\n \\_ Haraka (worker) cn=2296 cc=218 cps=2/5.2/20 rcpts=29300/1.78 rps=117/66.4/125 msgs=16489/7.18 mps=40/37.37/66 out=0/0/0\n \\_ Haraka (worker) cn=2091 cc=195 cps=4/4.74/16 rcpts=25646/1.71 rps=84/58.12/117 msgs=14982/7.16 mps=52/33.95/66 out=0/0/0\n```\n\nwhere:\n\n- cn = Total number of connections\n- cc = Total number of concurrent connections\n- cps = Number of connections in the last second / average / maximum\n- rcpts = Total number of recipients / Average number of recipients per message\n- rps = Number of recipients in the last second / average / maximum\n- msgs = Total number of messages / Average number messages per connection\n- mps = Number of messages in the last second / average / maximum\n- out = Mails being processed / Mails waiting to be processed / Mails in temp fail state\n- respawn = Number of worker processes respawned (only under cluster)\n\nIf 'cluster' is used then the master process will show the total\nacross all workers, with the exception of outbound stats.\n\nAll of the counts shown are since the process started, so if a\nworker has been re-started then the counts may not add up.\n\nNote: this plugin should be added at the top of `config/plugins` so\nthat its `connect_init`, `rcpt`, `data`, and `disconnect` hooks run\nbefore any plugin that might short-circuit those hooks.\n","docs/plugins/queue/deliver.md":"# queue/deliver\n\nThis plugin is now redundant. Outbound delivery is now built into Haraka.\n","docs/plugins/queue/discard.md":"# discard\n\nThis plugin will discard a message by pretending that the message was queued.\n\nIt is designed to be used by other plugins which request the message be\ndiscard by setting a connection or transaction note that this plugin\nchecks.\n\nIt uses the 'queue' hook, so it runs after all the plugins that hook on `data_post`.\n\nIf you use the 'quarantine' plug-in then this plugin should run _after_ it.\n\nUSE THIS PLUGIN WITH CARE!\n\n# Enable\n\nEnable by adding a `queue/discard` entry in `config/plugins` **before** your\nother queue plugins that perform actual deliveries.\n\n# Usage\n\nSet\n\n```javascript\nconnection.notes.discard = [1 | true]\n```\n\nor\n\n```javascript\nconnection.transaction.notes.discard = [1 | true]\n```\n","docs/plugins/queue/lmtp.md":"# queue/lmtp\n\nThis plugin delivers inbound mail via LMTP.\n\n## Configuration\n\nLMTP is enabled by adding `queue/lmtp` to config/plugins. LMTP delivery is configured in `config/lmtp.ini` . By default, all inbound messages are forwarded to the host specified in the `[main]` section. Domain specific routes can be specified by creating additional sections with the same host/port or path options.\n\n### lmtp.ini\n\n```ini\n; defaults\nhost=localhost\nport=24\n\n[example1.com]\n; Goes elsewhere\nhost=10.1.1.1\nport=2400\n\n[example2.com]\n; Using unix domain sockets\npath = /tmp/blah_com_socket\n```\n","docs/plugins/queue/qmail-queue.md":"# queue/qmail-queue\n\nThis plugin delivers the mail to the `qmail-queue` program, which can be used\nfor both inbound and outbound delivery.\n\n## Configuration\n\n- qmail-queue.path\n\n  The path to the `qmail-queue` binary. Default: `/var/qmail/bin/qmail-queue`\n\n- qmail-queue.ini\n  - enable_outbound=true\n\n    Deliver outbound email to qmail. Set to false to use Haraka's\n    separate Outbound mail routing (MX based delivery)).\n","docs/plugins/queue/quarantine.md":"# quarantine\n\nThis plugin will save a message (in message/rfc822 format) to a specified\ndirectory, which will be created automatically if it does not already exist,\na dated sub-folder is also added to the end of the path specified in YYYYMMDD\nformat.\n\nIt is designed to be used by other plugins which request the message be\nquarantined by setting a connection or transaction note that this plugin\nchecks.\n\nNOTE: this plugin simply saves a copy of the message. It does not reject or\ndiscard the message and relies on another plugin to perform this function.\n\nIt uses the 'queue' hook, so that it runs after all the 'data_post' plugins\nand should be listed in 'config/plugins' to run before your queue hooks that\nperform actual deliveries.\n\nTo ensure that only completely written files are present in the quarantine,\nthe files are written to a temporary directory first and then hardlinked to\nthe final destination before the temporary file is deleted.\n\nThe temporary directory is 'quarantine_path/tmp' which defaults to:\n/var/spool/haraka/quarantine/tmp.\n\nUpon start-up, any files present in the temporary directory are deleted\nsyncronously prior to any messages being accepted.\n\n## Configuration\n\nThis plugin looks for 'quarantine.ini' in the config directory.\n\n- quarantine_path (default: /var/spool/haraka/quarantine)\n\n  The default base path to save the quarantine files to. It will be created\n  if it does not already exist.\n\n## Usage\n\nIf you wish to keep a copy of the message in your plugin, simply either:\n\n```javascript\nconnection.notes.quarantine = [1 | true | 'sub/directory/path']\n```\n\nor\n\n```javascript\nconnection.transaction.notes.quarantine = [1 | true | 'sub/directory/path']\n```\n\ne.g.\n\n```javascript\nconnection.notes.quarantine = 1\n```\n\nwould save the message to '/var/spool/quarantine/haraka/YYYYMMDD/UUID' where\nYYYMMDD and UUID are expanded to current date and transaction UUID.\n\nand\n\n```javascript\nconnection.notes.quarantine = 'corpus'\n```\n\nwould save the message to '/var/spool/quarantine/haraka/corpus/YYYYMMDD/UUID'.\n\nNote: you can specify 'corpus/foo' or 'corpus/foo/bar' and the directories will\nbe automatically created. Do not add any leading or trailing slashes.\n\nBy default - after the message is quarantined, the plugin will tell Haraka to\ncontinue to the next plugin. You can specify a different action like DENY or\nOK and supply an optional message using the following notes:\n\n```javascript\nconnection.notes.quarantine_action = [OK, 'Message quarantined']\nconnection.transaction.notes.quarantine_action = [DENY, 'Message rejected']\n```\n\nIf you don't want to supply a specific message back to the client you can\nalso just specify a return code:\n\n```javascript\nconnection.notes.quarantine_action = OK\nconnection.transaction.notes.quarantine_action = DENY\n```\n","docs/plugins/queue/smtp_bridge.md":"# queue/smtp_bridge\n\nThis plugin delivers to another SMTP server, bridging the authentication\ndetails and post data from the initial connection.\n\nThis plugin is meant to be used with the plugin `auth/auth_bridge`.\n\nIt is different than `queue/smtp_proxy` or `queue/smpt_forward` because\nit doesn't use the AUTH details from a configuration file. This plugins\nsimply post the data from the original connection to the remote SMTP server\nusing the original AUTH details.\n\n## Configuration\n\nConfiguration is stored in `config/smtp_bridge.ini` and uses the INI\nstyle formatting.\n\nThe configuration of this plugin is simple:\n\n    host=localhost\n    #port=\n    #auth_type=\n    #priority=10\n\n- host: the host where you will be authenticating and posting,\n  for example `smtp.host.tld`. This is the only setting required.\n\nIf needed you can also set\n\n- port: default to empty and Haraka will use 25.\n- auth_type: default to empty and Haraka will try to pick an appropriate method.\n- priority: default to 10.\n","docs/plugins/queue/smtp_forward.md":"# queue/smtp_forward\n\n==================\n\nThis plugin delivers to another mail server. This is a common setup when you want to have a mail server with a solid pedigree of outbound delivery to other hosts, and inbound delivery to users.\n\nIn comparison to `queue/smtp_proxy`, this plugin waits until queue time to attempt the ongoing connection. This can be a benefit in reducing connections to your inbound mail server when you have content filtering (such as spamassassin) enabled. A possible downside is that it also delays recipient validation that the ongoing mail server may provide until queue time.\n\n## Configuration\n\n---\n\nConfiguration is stored in smtp_forward.ini in the following keys:\n\n- enable_outbound=[true]\n\n  SMTP forward outbound messages (set to false to enable Haraka's separate Outbound mail routing (MX based delivery)).\n\n- host=HOST\n\n  The host to connect to.\n\n- port=PORT\n\n  The port to connect to. Default: 25\n\n- connect_timeout=SECONDS\n\n  The maximum amount of time to wait when creating a new connection to the host. Default: 30 seconds.\n\n- timeout=SECONDS\n\n  The amount of seconds to let a backend connection live idle in the connection pool. This should always be less than the global plugin timeout, which should in turn be less than the connection timeout.\n\n- max_connections=NUMBER\n\n  Maximum number of connections at any given time. Default: 1000\n\n- enable_tls=[true]\n\n  Enable opportunistic TLS with the forward host via `STARTTLS` (if the host advertises it). This plugin does not work with implicit SMTP over TLS.\n\n- auth_type=[plain\\|login]\n\n  Enable PLAIN or LOGIN SMTP AUTH. This is required to enable AUTH.\n\n- auth_user=USERNAME\n\n  SMTP AUTH username to use.\n\n- auth_pass=PASSWORD\n\n  SMTP AUTH password to use.\n\n- queue\n\n  Which queue plugin to use. Default: undefined. The default bahavior is to use smtp_forward for inbound connections and outbound for relaying connections. This option is used for complex mail routes.\n\n- check_sender=false\n\n  Requires that sender domains defined in smtp_forward.ini (see Per-Domain below) have relaying privileges. This is a form of spoof prevention and assumes that any mail clients have relaying or AUTH privileges. This is usually the case.\n\n- check_recipient=false\n\n  By default, Haraka accepts no emails until a recipient plugin has been configured to accept mails for a domain. The simplest common case is the in_host_list plugin with a list of domains in config/host_list. An alternative is to set `check_recipient=true` and list each domain in a definition block in smtp_forward.ini (see Per-Domain Configuration). An example for two domains:\n\n  [example.com]\n  [example.net]\n\n- [tls]\n\nClient STARTTLS options are assembled by merging:\n\n1. `tls.ini` `[main]` — the global Haraka TLS config\n2. `smtp_forward.ini` `[tls]` — overrides. Anything set here wins.\n\nExample `smtp_forward.ini` `[tls]` section:\n\n    [tls]\n    rejectUnauthorized=true\n    minVersion=TLSv1.2\n    no_tls_hosts[]=10.0.0.5\n\nPer-domain `enable_tls=false` still disables STARTTLS for that backend. Per-domain TLS cipher/cert overrides are not currently supported.\n\nChanges to `tls.ini` require a Haraka restart to apply to the forward path; changes to `smtp_forward.ini` are picked up by the existing reload hook.\n\n# Per-Domain Configuration\n\nMore specific forward routes for domains can be defined. The domain is chosen based on the value of the `domain_selector` config variable.\n\nWhen `domain_selector` is set to `rcpt_to` (the default), more specific routes are only honored for SMTP connections with a single recipient or SMTP connections where every recipient host is identical.\n\nWhen `domain_selector` is set to `mail_from`, it first searches for configuration using the complete email address, falls back to the domain if not found, then to main configuration.\n\nenable_outbound can be set or unset on a per-domain level to enable or disable forwarding for specific domains.\n\n    # default SMTP host\n    host=1.2.3.4\n    # auth_type=plain\n    # auth_user=user\n    # auth_user=pass\n\n    [example1.com]\n    host=1.2.3.5\n    # auth_type=plain\n    # auth_user=user\n    # auth_pass=pass\n\n    [email@example1.com]\n    host=1.2.3.5\n    # auth_type=plain\n    # auth_user=user\n    # auth_pass=pass\n\n    [example2.com]\n    host=1.2.3.5\n\n    [example3.com]\n    host=1.2.3.6\n\n    [example4.com]\n    enable\\_outbound=false\n\n# Split host forward routing\n\nWhen an incoming email transaction has multiple recipients with different forward routes, recipients to subsequent forward routes are deferred. Example: an incoming email transaction has recipients user@example1.com, user@example2.com, and user@example3.com. The first two recipients will be accepted (they share the same forward destination) and the latter will be deferred. It will arrive in a future delivery attempt by the remote.\n","docs/plugins/queue/smtp_proxy.md":"# queue/smtp_proxy\n\n================\n\nThis plugin delivers to another mail server. This is a common setup when you want to have a mail server with a solid pedigree of outbound delivery to other hosts, and inbound delivery to users.\n\nIn comparison to `queue/smtp_forward`, this plugin makes a connection at MAIL FROM time to the ongoing SMTP server. This can be a benefit in that you get any SMTP-time filtering that the ongoing server provides, in particular one important facility to some setups is recipient filtering.\n\nBe aware that other than connect and HELO-time filtering, you will have as many connections to your ongoing SMTP server as you have to Haraka.\n\n## Configuration\n\n---\n\nConfiguration is stored in smtp_proxy.ini in the following keys:\n\n- enable_outbound=[true]\n\n  SMTP proxy outbound messages (set to false to enable Haraka's\n  separate Outbound mail routing (MX based delivery)).\n\n- host=HOST\n\n  The host to connect to.\n\n- port=PORT\n\n  The port to connect to.\n\n- connect_timeout=SECONDS\n\n  The maximum amount of time to wait when creating a new connection\n  to the host. Default if unspecified is 30 seconds.\n\n- timeout=SECONDS\n\n  The amount of seconds to let a backend connection live idle in the\n  proxy pool. This should always be less than the global plugin timeout,\n  which should in turn be less than the connection timeout.\n\n- max_connections=NUMBER\n\n  Maximum number of connections to create at any given time.\n\n- enable_tls=[true|yes|1]\n\n  Enable opportunistic TLS with the forward host via `STARTTLS` (if the host advertises it).\n\n- auth_type=[plain|login]\n\n  Enable PLAIN or LOGIN SMTP AUTH. This is required to enable AUTH.\n\n- auth_user=USERNAME\n\n  SMTP AUTH username to use.\n\n- auth_pass=PASSWORD\n\n  SMTP AUTH password to use.\n\n- [tls]\n\nClient STARTTLS options are assembled by merging:\n\n1. `tls.ini` `[main]` — the global Haraka TLS config.\n2. `smtp_proxy.ini` `[tls]` — overrides. Anything set here wins.\n\nChanges to `tls.ini` require a Haraka restart to apply to the proxy path; changes to `smtp_proxy.ini` are picked up by the existing reload hook.\n","docs/plugins/queue/test.md":"# queue/test\n\nThis plugin saves incoming E-Mail to your temporary directory, as `mail_{message_id}.eml`, where message_id is a UUID.\n\nThis plugin can be useful to quickly test if you're able to receive incoming E-Mail and just dump them to disk.\n\nThe temporary directory is determined using Node's [`os.tmpdir()`](https://nodejs.org/api/os.html#ostmpdir), which respects standard platform configurations.\n","docs/plugins/rcpt_to.in_host_list.md":"# rcpt_to.in_host_list\n\nThis plugin is the mainstay of an inbound Haraka server. It should list the\ndomains that are local to the host. Mails that have RCPT TO not matching\na host in the given list will be passed onto other rcpt hooks. If no rcpt\nhook accepts the connection, it will be rejected.\n\n## Configuration\n\n- host_list\n\n  Specifies the list of hosts that are local to this server.\n\n- host_list_regex\n\n  Specifies the list of regexes that are local to this server. Note\n  all these regexes are anchored with ^regex$. One can choose not to\n  anchor with .\\*. There is the potential for bad regexes to be\n  too permissive if we don't anchor.\n\n- host_list.anti_spoof\n\n  When enabled, this will cause Haraka to reject any MAIL FROM where\n  the host appears within the host list but the connected host is not\n  a relay, e.g. connection.relaying is not set either by SMTP AUTH or\n  another plugin like 'relay'.\n\n## Relaying\n\nThis plugin checks to see if the MAIL FROM domain is local. When\nconnection.relaying is detected (haraka -h relay) and the MAIL FROM domain is\nlocal, this plugin will vouch for any RCPT. This limits relaying users to\nsending from local domains, which is much safer than letting relay clients\nsend from any domain.\n","docs/plugins/rcpt_to.max_count.md":"# rcpt_to.max_count\n\nThe functionality of this plugin was integrated in to [haraka-plugin-limit](https://github.com/haraka/haraka-plugin-limit).\n","docs/plugins/record_envelope_addresses.md":"# record_envelope_addresses\n\nThis plugin adds two new header lines.\n\n- X-Envelope-To: the envelope RCPT TO address\n- X-Envelope-From: the envelope MAIL FROM address\n\nIt is useful if you need to know the exact addresses used to send an email, e.g. when\nthe email was sent to you as BCC or if it is a newsletter. In both cases the recipient\naddress is normally not recorded in the headers.\n\n## Caveats\n\nIf you enable this plugin you may introduce a possible information leak, i.e. disclosure\nof BCC recipients. So you never want to use this on an outgoing mail server and maybe also\nnot if this server is used as a relay.\n\n## Configuration\n\nThis plugin has no configuration.\n","docs/plugins/relay.md":"# relay\n\nRepackaged as [haraka-plugin-relay](https://github.com/haraka/haraka-plugin-relay).\n","docs/plugins/reseed_rng.md":"# reseed_rng\n\nReseeds `Math.random()` in each cluster worker at start-up using\n`crypto.randomBytes(256)`. Without this, workers forked at nearly the\nsame time can end up with correlated PRNG state, which can produce\nUUID collisions and other \"this should be impossible\" bugs.\n\nThe plugin relies on [seedrandom](https://www.npmjs.com/package/seedrandom)\nbeing loaded so that `Math.seedrandom()` is available.\n\nAnyone running with `nodes=...` in `smtp.ini` (i.e. cluster mode) should\nconsider enabling this plugin.\n\n## Configuration\n\nNo configuration.\n","docs/plugins/status.md":"# Status\n\nThis plugin allows to get internal status of queues and pools with SMTP commands sent from localhost.\n\n## Communication\n\n- **Request** &rarr; `STATUS <CMD> [param1] [param2]....`\n- **Response** &larr; _&lt;SMTP code 211 or 500>&lt;space>&lt;json encoded response>\\r\\n_\n\n### Example\n\n```\n< 220 example.com ESMTP Haraka ready\n> STATUS QUEUE INSPECT\n< 211 {\"delivery_queue\":[],\"temp_fail_queue\":[]}\n```\n\n## Available commands list\n\n- `STATUS POOL LIST` - map of active outbound connection pools, keyed by `host:port`\n- `STATUS QUEUE STATS` - queue statistics in format `\"<in_progress>/<delivery_queue length>/<temp_fail_queue length>\"`\n- `STATUS QUEUE LIST` - list of queue files on disk with _uuid, domain, mail_from, rcpt_to_ attributes\n- `STATUS QUEUE INSPECT` - returns merged content of `outbound.delivery_queue` and `outbound.temp_fail_queue` across all workers\n- `STATUS QUEUE DISCARD file` - stop delivering email file\n- `STATUS QUEUE PUSH file` - try to re-deliver email immediately\n\n## Notes\n\n### Live data only\n\n`POOL LIST`, `QUEUE STATS`, and `QUEUE INSPECT` reflect live in-memory state. They show only messages currently being processed or waiting in the retry queue. `QUEUE LIST` reads queue files from disk and may show messages that have already been delivered if they haven't been cleaned up yet.\n\n### Cluster mode\n\nIn cluster mode, `POOL LIST`, `QUEUE STATS`, and `QUEUE INSPECT` aggregate results from all worker processes into a single response:\n\n- `POOL LIST` — pool maps from all workers are merged into one object\n- `QUEUE STATS` — counters from all workers are summed into a single `\"N/N/N\"` string\n- `QUEUE INSPECT` — `delivery_queue` and `temp_fail_queue` arrays from all workers are concatenated\n\n`QUEUE LIST` always runs on the master process since it reads shared queue files from disk.\n","docs/plugins/tarpit.md":"# tarpit\n\nThis plugin is designed to introduce deliberate delays on the response\nof every hook in order to slow down a connection. It has no\nconfiguration and is designed to be used only by other plugins.\n\nIt must be loaded early in config/plugins (e.g. before any plugins\nthat accept recipients or that return OK) but must be loaded _after_\nany plugins that wish to use it.\n\n## Usage\n\nTo use this plugin in another plugin set:\n\n    connection.notes.tarpit = <seconds to delay>;\n\nor\n\n    connection.transaction.notes.tarpit = <seconds to delay>;\n\n## Configuration\n\nThe configuration file for tarpit is config/tarpit.ini.\n\n- hooks_to_delay - a list of hooks to delay at. This setting can be used to\n  override the default list in the plugin. For example, if you notice that\n  malware is disconnecting after delaying rcpt_ok, you can remove just that\n  hook from the list:\n\nhooks_to_delay=connect,helo,ehlo,mail,rcpt,data,data_post,queue,unrecognized_command,vrfy,noop,rset,quit\n\n## Plugin Timeout\n\nconfig/tarpit.timeout (Default: 0)\n\nAll Haraka plugins can configure a _name_.timeout file. The timeout specifies\nhow long Haraka lets the plugin do nothing before it times out. When zero,\nthere is no timeout. When non-zero and _seconds to delay_ is longer than\ntarpit.timeout (default: 1s), you'll get errors like this in your log files:\n\n    [core] Plugin tarpit timed out on hook rcpt - make sure it calls the callback\n    [core] Plugin tarpit timed out on hook quit - make sure it calls the callback\n\nThe solution is to set the contents of config/tarpit.timeout to zero or\n**seconds to delay** + 1.\n\n## Logging\n\nWhen tarpitting a command it will log 'tarpitting response for Ns' to\nthe INFO facility where N is the number of seconds.\n","docs/plugins/tls.md":"# tls\n\nThis plugin enables the use of TLS (via `STARTTLS`) in Haraka.\n\nFor this plugin to work you must have SSL certificates installed correctly.\n\nHaraka has [SNI](https://en.wikipedia.org/wiki/Server_Name_Indication) support. When the remote MUA/MTA presents a servername during the TLS handshake and a TLS certificate with that Common Name matches, that certificate will be presented. If no match is found, the default certificate (see Certificate Files) is presented.\n\n## Certificate Files\n\nDefaults settings are shown and can be overridden in `config/tls.ini`.\n\n```ini\nkey=tls_key.pem\ncert=tls_cert.pem\ndhparam=dhparams.pem\n```\n\n## Certificate Directory\n\nIf the directory `config/tls` exists, files within the directory are PEM encoded TLS files in one of two formats: bundles or Wild Wild West.\n\n### Certificate bundles\n\nGenerate PEM bundles in The Usual Way[TM] by concatenating the key, certificate, and CA/chain certs in that order. Example:\n\n```sh\ncat example.com.key example.com.crt ca-int.crt > haraka/config/tls/example.com.pem\n```\n\nAn example [acme.sh](https://acme.sh) deployment [script](https://github.com/msimerson/Mail-Toaster-6/blob/master/provision/letsencrypt.sh) installs [Let's Encrypt](https://letsencrypt.org) certificate bundles to the Haraka `config/tls`directory.\n\n### Wild Wild West\n\nPEM encoded TLS certificates and keys can be stored in files in `config/tls`. The certificate loader is recursive, so TLS files can be in subdirs like `config/tls/mx1.example.com`. The certificate names are parsed from the 1st cert in each file and indexed by the certs Common Name(s). Subject Alternate Names are supported. The file name containing the certificates does _not_ matter. Additional certificates within each file are presumed to be CA chain (intermediate) certificates.\n\nIf the TLS key is stored in the same file as the matching certificate, then the name of the file does not matter. If the TLS key is alone in a file, the file MUST be named with the keys Common Name. The file extension does not matter, `.pem` and `.key` are common. If the key is used for multiple CNs, the key must be stored in a file name matching each CN. Examples of working TLS key/cert file pairs for the Common Name mx1.example.com:\n\n1. certificate bundle (see above), key & cert in same file\n   - config/tls/mx1.example.com.pem (recommended)\n   - config/tls/any-unique-name.pem (CN is extracted from 1st cert)\n2. files in TLS dir\n   - config/tls/mx1.example.com.crt\n   - config/tls/mx1.example.com.key\n3. files in subdir\n   - config/tls/example.com/mx1.cert\n   - config/tls/example.com/mx1.example.com.key\n4. wildcard bundle on Windows platform (\\* is not allowed in file names)\n   - config/tls/\\_.example.com.pem\n\n## Purchased Certificate\n\nFor purchased certificate, append any intermediate/chained/ca-cert files to the certificate in this order:\n\n1. The CA signed SSL cert\n2. Any intermediate certificates\n3. The CA root certificate\n\nSee also [Setting Up TLS](https://github.com/haraka/Haraka/wiki/Setting-up-TLS-with-CA-certificates)\n\n## Self Issued (unsigned) Certificate\n\nCreate a certificate and key file in the config directory with the following command:\n\n    openssl req -x509 -nodes -days 2190 -newkey rsa:2048 \\\n            -keyout config/tls_key.pem -out config/tls_cert.pem\n\nYou will be prompted to provide details of your organization. Make sure the\nCommon Name is set to your servers Fully Qualified Domain Name, which should\nbe the same as the contents of your `config/me` file.\n\n## Configuration\n\nThe following settings can be specified in `config/tls.ini`.\n\n### key\n\nSpecifies an alternative location for the key file. For multiple keys, use `key[]=` assignment for each. Non-absolute paths are relative to the `config/` directory.\n\nTo configure a single key and a cert chain, located in the `config/`\ndirectory, use the following in `tls.ini`:\n\n```ini\nkey=example.com.key.pem\ncert=example.com.crt-chain.pem\n```\n\nTo use multiple pairs of key and cert chain files outside of the haraka\n`config/` directory, configure instead:\n\n```ini\nkey[]=/etc/ssl/private/example.com.rsa.key.pem\ncert[]=/etc/ssl/private/example.com.rsa.crt-chain.pem\nkey[]=/etc/ssl/private/example.com.ecdsa.key.pem\ncert[]=/etc/ssl/private/example.com.ecdsa.crt-chain.pem\n```\n\n### cert\n\nSpecifies the location(s) for the certificate chain file. For multiple certificate chains, use `cert[]=` assignment for each. Non-absolute paths are relative to the `config/` directory. See the description of the `key` parameter for specific use.\n\n### no_tls_hosts\n\nIf needed, add this section to the `config/tls.ini` file and list any IP ranges that have broken TLS hosts. Ex:\n\n```ini\n[no_tls_hosts]\n192.168.1.3\n172.16.0.0/16\n```\n\nNote: `[no_tls_hosts]` section applies to inbound only. For outbound mail, this feature is implemented as an array like `force_tls_hosts`:\n\n```ini\n[outbound]\nno_tls_hosts[]=192.168.1.3\nno_tls_hosts[]=172.16.0.0/16\n```\n\nThe [Node.js TLS](http://nodejs.org/api/tls.html) page has additional information about the following options.\n\n### no_starttls_ports\n\nAn array of incoming ports on which Haraka will not advertise STARTTLS capability.\n\n```ini\nno_starttls_ports[]=2525\n```\n\n### force_tls_hosts\n\nFor known good TLS hosts, it's possible to force that the outbound mailer will only connect via secure sockets. This makes Haraka use _forced TLS_ instead of _opportunistic TLS_. For forced TLS, the STARTTLS upgrade must succeed with a valid certificate (overriding `rejectUnauthorized`). The list is matched both against the host (MX record or `nexthop` in `relay_dest_domains.ini`), and the domain name of the email address.\n\nNote: unlike `no_tls_hosts`, this feature is implemented as an array:\n\n```ini\n[outbound]\nforce_tls_hosts[]=172.17.123.1\nforce_tls_hosts[]=172.17.124.0/24\nforce_tls_hosts[]=mx.example.org\nforce_tls_hosts[]=example.com\n```\n\n### ciphers\n\nA list of allowable ciphers to use. Example:\n\n    ciphers=EECDH+AESGCM:EDH+aRSA+AESGCM:EECDH+AES256:EDH+aRSA+AES256:EECDH+AES128:EDH+aRSA+AES128:RSA+AES:RSA+3DES\n\nSee also: [Mozilla SSL configuration generator](https://ssl-config.mozilla.org/) and the [SSLlabs Test Page](https://www.ssllabs.com/ssltest/index.html)\n\n### minVersion\n\nSpecifies minimum allowable TLS protocol version to use. Example:\n\n     minVersion=TLSv1.1\n\nIf unset, the default is Node's `tls.DEFAULT_MIN_VERSION` constant\n(currently `'TLSv1.2'`). Valid values: `'TLSv1.3'`, `'TLSv1.2'`,\n`'TLSv1.1'`, `'TLSv1'`.\n\n### honorCipherOrder\n\nIf specified, the list of configured ciphers is treated as the cipher priority from highest to lowest. The first matching cipher will be used, instead of letting the client choose. The default is `true`.\n\n### ecdhCurve\n\nSpecifies the elliptic curve used for ECDH or ECDHE ciphers.\nOnly one curve can be specified. The default is `prime256v1` (NIST P-256).\n\n### dhparam\n\nSpecifies the file containing the diffie-hellman parameters to use for DH or DHE key exchange. If this param or file is missing, it will be generated automatically. Default: `dhparams.pem`.\n\n### requestCert\n\nWhether Haraka should request a certificate from a connecting client.\n\n    requestCert=[true|false]  (default: true)\n\n### rejectUnauthorized\n\nReject connections from clients without a CA validated TLS certificate.\n\n    rejectUnauthorized=[true|false]  (default: false)\n\n### requireAuthorized\n\nWhen `rejectUnauthorized=false`, require validated TLS certificates on just the specified ports.\n\n```ini\nrequireAuthorized[]=465\n;requireAuthorized[]=587\n```\n\n### secureProtocol\n\nLegacy. Specifies the OpenSSL API function used to negotiate TLS — see\nthe [OpenSSL API page](https://www.openssl.org/docs/manmaster/ssl/ssl.html).\nPrefer `minVersion` for modern setups; `secureProtocol` is only useful\nto lock to a specific historic protocol.\n\n### requestOCSP\n\nSpecifies that OCSP Stapling should be enabled, according to RFC 6066.\nStapling of OCSP messages allows the client to receive these along the\nTLS session setup instead of delaying the session setup by requiring a\nseparate http connection to the OCSP server.\n\n    requestOCSP=[true|false]  (default: false)\n\nOCSP responses from the OCSP server are cached in memory for as long as\nthey are valid, and get refreshed after that time. A server restart\nrequires the OCSP responses to be fetched again upon the first client\nconnection.\n\n## Inbound Specific Configuration\n\nBy default the above options are shared with outbound mail (either\nusing `smtp_forward`, `smtp_proxy` or plain outbound mail heading to\nan external destination). To make these options specific to inbound\nmail, put them under an `[inbound]` parameter group. Outbound options\ncan go under an `[outbound]` parameter group, and plugins that use\nSMTP tls for queueing such as `smtp_proxy` and `smtp_forward` can\nuse that plugin name for plugin specific options.\n\n## `[redis]` section\n\nThis section is mainly used to enable so called _TLS NO-GO_ feature that essentially stops advertising/using TLS if there was a problem setting it up previously. We use `no_tls|ip.add.re.ss` key to store the flag in redis. There are a couple of settings that control the behavior:\n\n`disable_for_failed_hosts = true` to enable the feature\n\n`disable_expiry = 604800` to set for how long we disable TLS for failing host, in seconds\n\n`disable_inbound_expiry = 3600` same as above, but applies to inbound (aka STARTTLS capability) only\n","docs/plugins/toobusy.md":"# toobusy\n\nThis plugin will stop Haraka accepting new connections when the event loop\nlatency is too high.\n\nSee https://github.com/STRML/node-toobusy for details.\n\nTo use this plugin you must install the [`toobusy-js`](https://www.npmjs.com/package/toobusy-js)\nmodule — it is not bundled with Haraka. From your Haraka install\ndirectory:\n\n```sh\nnpm install toobusy-js\n```\n\nThis plugin registers on the `connect` hook with priority `-100`, so it\nruns ahead of other `connect`/`lookup_rdns` plugins. Listing it near the\ntop of `config/plugins` is still a good idea for clarity.\n\n## Configuration\n\nIf you wish to override the default maxLag value of 70ms then add the desired\nvalue to config/toobusy.maxlag. This can be set and changed at runtime and\nno restart is required.\n\nNote that if you set the maxLag value to <10 then this will cause the toobusy\nmodule to raise an exception which will cause Haraka to stop.\n","docs/plugins/xclient.md":"# xclient\n\nImplements the [XCLIENT](http://www.postfix.org/XCLIENT_README.html) protocol.\n\n## configuration\n\n- xclient.hosts\n\n  A list of IP addresses, one per line that should be allowed to use the\n  XCLIENT protocol. Localhost (127.0.0.1 or ::1) is allowed implicitly.\n"},"files":{".github/copilot-instructions.md":"<!-- agents-version: 3 -->\n\n# AGENTS.md\n\nShared instructions for every coding agent in this repo. The tool-specific files\n(`CLAUDE.md`, `GEMINI.md`, `.github/copilot-instructions.md`) defer to this one.\nIf a package-level instruction file exists, it is authoritative for that package.\n\n## Repository model\n\n- This file is shared across every Haraka repository. It describes the whole family — not just this repo. Each repo is an independent npm package, there is no root workspace. Run every command from the package root.\n- Repos are worked on both standalone and as sibling checkouts in a combined tree (`Haraka/`, `plugin/<name>/`, …). Don't assume sibling packages are present on disk.\n- The family:\n  - `Haraka` — core SMTP server (has `./run_tests`).\n  - `haraka-plugin-<name>` — optional plugins; may depend on each other (e.g. bounce → spf).\n  - `haraka-config` — config loader with hot-reload.\n  - `haraka-results`, `haraka-notes` — per-connection result / note tracking.\n  - `haraka-net-utils`, `haraka-utils`, `haraka-constants`, `haraka-dsn`, `haraka-tld`, `haraka-message-stream` — shared libraries.\n  - `@haraka/email-address` — the RFC 5321/5322 parser (supersedes the deprecated `address-rfc282x`).\n  - `@haraka/eslint-config`, `haraka-test-fixtures`.\n\n## Working agreement\n\n- Do only what was asked. When you spot an adjacent bug or smell, surface it and ask before expanding scope. Don't silently refactor, and don't ignore it.\n- Preserve compatibility; break it only for an explicit, stated reason.\n- For protocol behavior, identify the relevant RFC and verify conformance against the existing implementation.\n\n## Source control\n\n- Never run history- or remote-mutating commands (`git commit`, `git push`, `git tag`, `gh pr create`) unless explicitly asked.\n- create diffs; the human reviews, commits, and pushes.\n- No trailers on commit messages or PR bodies.\n- When asked to generate a PR:\n  - keep it succinct, terse, and DRY\n  - Post a link to it in the console\n- Document changes relative to main in CHANGELOG.md\n  - one clear concise entry per change, < 50 chars\n  - prefer Conventional Commit format, imperative mood.\n  - Rationale belongs in the code or PR, not the bullet.\n\n## Coding standards\n\n- Target current Node LTS; prefer ES2024 over legacy patterns.\n- Existing code is CommonJS (`require`/`exports`) — match it. New modules should use ESM with CJS interop (see `@haraka/email-address`).\n- Add `node:` prefixes to built-in requires in any file you touch (`require('fs')` → `require('node:fs')`).\n- Prefer: promise APIs (`fs/promises`), `for...of`/`for...in` over `forEach`, `node:readline` for line parsing, template literals over concatenation, `true`/`false` over `1`/`0`, and guard-style early returns.\n- Remove commented-out code (it lives in git history). `npm run qlty` must pass without warnings.\n\n## Concision\n\n- Don't Repeat Yourself: Say each thing exactly once, in the place it belongs.\n- Sentences: must carry non-obvious facts the reader cannot get from the surroundings.\n- Cut scaffolding, keep facts.\n\nNever write these:\n\n- **Announcements.** \"One question:\", \"Two reasons:\", \"The point is:\", \"Here's the thing:\". Say the thing. If you announce a count, the count must be right — so don't announce one.\n- **Justification tails.** A clause after a comma or dash that argues for the clause before it: \"…and a guess there is worse than none\", \"…which is the failure this exists to prevent\".\n- **The contrast tic.** \"X, not Y\" and \"X rather than Y\" where Y is obvious. State X.\n- **Clauses doing an adjective's job.** \"a setting nobody has written\" is \"an unwritten setting\"; \"a copy nobody has opened\" is \"an unopened copy\"; \"still has to\" is \"must\". A state with a name takes the name.\n- **Closing morals.** A final sentence restating the paragraph's point.\n\n## Comments\n\n- Keep only WHY comments — a hidden constraint, an invariant, a workaround for a specific bug, or an RFC citation that explains otherwise-surprising behavior.\n- Delete WHAT comments that restate the code.\n- When a rename makes a comment redundant, delete it.\n- Never narrate history or audit findings.\n- Never count the code — counts rot.\n- Prefer self-documenting code: a better name beats any comment.\n\n## Haraka plugins\n\n- Full hook/API reference: `docs/Plugins.md` in the `Haraka` core repo.\n- A plugin is an npm package: `index.js` (`exports.register` + hook handlers), `config/` (default `.ini`/`.json`/`.yaml`), `test/`, `README.md`.\n- Register hooks in `exports.register` with `this.register_hook('phase', 'method'[, priority])`.\n- Hook handlers take `(next, connection)` (rcpt hooks also take `rcpt`) and must call `next` exactly once. Gate early — return `next()` on missing transaction, disabled config, or skip conditions. Signal a verdict with `next(DENY|DENYSOFT|OK, msg)`; `DENY`/`OK`/etc. are plugin-scope globals (no import).\n- Results: `connection.transaction.results.add(this, { pass|fail|skip|msg|err, emit })`; query with `results.has(plugin, list, search)`. `emit: true` already logs the collated line — don't also `loginfo`/`logerror` the same thing. results.add(this, {err}) always logs.\n- Config loads via `config.get` with a hot-reload callback; declare every boolean or it stays a string and `=== true/false` silently fails:\n  ```js\n  this.cfg = this.config.get('name.ini', { booleans: ['+a.b', '-c.d'] }, () => this.load())\n  ```\n- Keep handlers thin. Push pure decision logic and I/O into `lib/*.js` as pure functions that return a verdict/value; the handler just maps that to `results.add` + `next`. For external I/O (DNS, network), expose an injectable seam — a swappable function whose default is the real implementation — so tests run without mocks.\n- If you add files outside `index.js` (e.g. a `lib/` dir), add them to `package.json` `files` so they publish.\n\n## Testing\n\n- Test real behavior and observable outcomes — `results`, return codes, emitted headers, side effects — not how a function was called. Asserting call shape (`calledWith`, arity, call counts) tests the test and hides signature drift.\n- Mocks/stubs are a smell. Prefer real inputs; when you must isolate a dependency, inject a seam and assert the outcome. Never leave a stub that neuters the path under test — that yields green tests proving nothing.\n- For bug fixes, add a failing test first, then fix.\n- Every feature ships with meaningful tests. A `.skip` is a coverage hole: fix it or delete it.\n- Use `node:test` and `node:assert/strict` for new tests and Mocha migrations. Plugin tests use `haraka-test-fixtures` (`makePlugin`, `makeConnection`, `callHook`).\n- Run the package's `lint`, `prettier`, and `format` before handoff.\n\n## Commands (run inside the target package)\n\n- Test: `npm test`. Single file: `node --test test/path/to/file.js`.\n- Haraka core repo only: `./run_tests [test/plugins/foo.js]`.\n- Coverage: `npm run test:coverage`; lcov: `npm run test:coverage:lcov`. Keep coverage at/above ~90%.\n- If coverage output includes non-source files (e.g. `package.json`, `test/*`), scope it with `--test-coverage-include` (preferred when the list is short) or `--test-coverage-exclude`.\n- Lint/format: `npm run lint` / `prettier` / `format`. Version drift: `npm run versions[:fix]`.\n\n## Package script parity\n\n- node:test packages should expose `test`, `test:coverage`, `test:coverage:lcov`, `lint`, `prettier`, `format` with matching shapes across siblings. Standardize on node:test coverage (not c8); add the canonical scripts when touching a package that lacks them:\n  ```jsonc\n  \"test:coverage\": \"node --test --experimental-test-coverage\",\n  \"test:coverage:lcov\": \"mkdir -p coverage && node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info\"\n  ```\n\n## Repo badges\n\n- Code climate is deprecated. Update with qlty.sh instead.\n- The canonical format for badges should be:\n  - Top of README.md:\n    - [![Test][ci-img]][ci-url] [![Cover][cov-img]][cov-url] [![Qlty][qlty-img]][qlty-url]\n  - Bottom of README.md:\n    - [ci-img]: https://github.com/haraka/<name>/actions/workflows/ci.yml/badge.svg\n    - [ci-url]: https://github.com/haraka/<name>/actions/workflows/ci.yml\n    - [cov-img]: https://codecov.io/github/haraka/<name>/coverage.svg\n    - [cov-url]: https://codecov.io/github/haraka/<name>\n    - [qlty-img]: https://qlty.sh/gh/haraka/projects/<name>/maintainability.svg\n    - [qlty-url]: https://qlty.sh/gh/haraka/projects/<name>\n","AGENTS.md":"<!-- agents-version: 3 -->\n\n# AGENTS.md\n\nShared instructions for every coding agent in this repo. The tool-specific files\n(`CLAUDE.md`, `GEMINI.md`, `.github/copilot-instructions.md`) defer to this one.\nIf a package-level instruction file exists, it is authoritative for that package.\n\n## Repository model\n\n- This file is shared across every Haraka repository. It describes the whole family — not just this repo. Each repo is an independent npm package, there is no root workspace. Run every command from the package root.\n- Repos are worked on both standalone and as sibling checkouts in a combined tree (`Haraka/`, `plugin/<name>/`, …). Don't assume sibling packages are present on disk.\n- The family:\n  - `Haraka` — core SMTP server (has `./run_tests`).\n  - `haraka-plugin-<name>` — optional plugins; may depend on each other (e.g. bounce → spf).\n  - `haraka-config` — config loader with hot-reload.\n  - `haraka-results`, `haraka-notes` — per-connection result / note tracking.\n  - `haraka-net-utils`, `haraka-utils`, `haraka-constants`, `haraka-dsn`, `haraka-tld`, `haraka-message-stream` — shared libraries.\n  - `@haraka/email-address` — the RFC 5321/5322 parser (supersedes the deprecated `address-rfc282x`).\n  - `@haraka/eslint-config`, `haraka-test-fixtures`.\n\n## Working agreement\n\n- Do only what was asked. When you spot an adjacent bug or smell, surface it and ask before expanding scope. Don't silently refactor, and don't ignore it.\n- Preserve compatibility; break it only for an explicit, stated reason.\n- For protocol behavior, identify the relevant RFC and verify conformance against the existing implementation.\n\n## Source control\n\n- Never run history- or remote-mutating commands (`git commit`, `git push`, `git tag`, `gh pr create`) unless explicitly asked.\n- create diffs; the human reviews, commits, and pushes.\n- No trailers on commit messages or PR bodies.\n- When asked to generate a PR:\n  - keep it succinct, terse, and DRY\n  - Post a link to it in the console\n- Document changes relative to main in CHANGELOG.md\n  - one clear concise entry per change, < 50 chars\n  - prefer Conventional Commit format, imperative mood.\n  - Rationale belongs in the code or PR, not the bullet.\n\n## Coding standards\n\n- Target current Node LTS; prefer ES2024 over legacy patterns.\n- Existing code is CommonJS (`require`/`exports`) — match it. New modules should use ESM with CJS interop (see `@haraka/email-address`).\n- Add `node:` prefixes to built-in requires in any file you touch (`require('fs')` → `require('node:fs')`).\n- Prefer: promise APIs (`fs/promises`), `for...of`/`for...in` over `forEach`, `node:readline` for line parsing, template literals over concatenation, `true`/`false` over `1`/`0`, and guard-style early returns.\n- Remove commented-out code (it lives in git history). `npm run qlty` must pass without warnings.\n\n## Concision\n\n- Don't Repeat Yourself: Say each thing exactly once, in the place it belongs.\n- Sentences: must carry non-obvious facts the reader cannot get from the surroundings.\n- Cut scaffolding, keep facts.\n\nNever write these:\n\n- **Announcements.** \"One question:\", \"Two reasons:\", \"The point is:\", \"Here's the thing:\". Say the thing. If you announce a count, the count must be right — so don't announce one.\n- **Justification tails.** A clause after a comma or dash that argues for the clause before it: \"…and a guess there is worse than none\", \"…which is the failure this exists to prevent\".\n- **The contrast tic.** \"X, not Y\" and \"X rather than Y\" where Y is obvious. State X.\n- **Clauses doing an adjective's job.** \"a setting nobody has written\" is \"an unwritten setting\"; \"a copy nobody has opened\" is \"an unopened copy\"; \"still has to\" is \"must\". A state with a name takes the name.\n- **Closing morals.** A final sentence restating the paragraph's point.\n\n## Comments\n\n- Keep only WHY comments — a hidden constraint, an invariant, a workaround for a specific bug, or an RFC citation that explains otherwise-surprising behavior.\n- Delete WHAT comments that restate the code.\n- When a rename makes a comment redundant, delete it.\n- Never narrate history or audit findings.\n- Never count the code — counts rot.\n- Prefer self-documenting code: a better name beats any comment.\n\n## Haraka plugins\n\n- Full hook/API reference: `docs/Plugins.md` in the `Haraka` core repo.\n- A plugin is an npm package: `index.js` (`exports.register` + hook handlers), `config/` (default `.ini`/`.json`/`.yaml`), `test/`, `README.md`.\n- Register hooks in `exports.register` with `this.register_hook('phase', 'method'[, priority])`.\n- Hook handlers take `(next, connection)` (rcpt hooks also take `rcpt`) and must call `next` exactly once. Gate early — return `next()` on missing transaction, disabled config, or skip conditions. Signal a verdict with `next(DENY|DENYSOFT|OK, msg)`; `DENY`/`OK`/etc. are plugin-scope globals (no import).\n- Results: `connection.transaction.results.add(this, { pass|fail|skip|msg|err, emit })`; query with `results.has(plugin, list, search)`. `emit: true` already logs the collated line — don't also `loginfo`/`logerror` the same thing. results.add(this, {err}) always logs.\n- Config loads via `config.get` with a hot-reload callback; declare every boolean or it stays a string and `=== true/false` silently fails:\n  ```js\n  this.cfg = this.config.get('name.ini', { booleans: ['+a.b', '-c.d'] }, () => this.load())\n  ```\n- Keep handlers thin. Push pure decision logic and I/O into `lib/*.js` as pure functions that return a verdict/value; the handler just maps that to `results.add` + `next`. For external I/O (DNS, network), expose an injectable seam — a swappable function whose default is the real implementation — so tests run without mocks.\n- If you add files outside `index.js` (e.g. a `lib/` dir), add them to `package.json` `files` so they publish.\n\n## Testing\n\n- Test real behavior and observable outcomes — `results`, return codes, emitted headers, side effects — not how a function was called. Asserting call shape (`calledWith`, arity, call counts) tests the test and hides signature drift.\n- Mocks/stubs are a smell. Prefer real inputs; when you must isolate a dependency, inject a seam and assert the outcome. Never leave a stub that neuters the path under test — that yields green tests proving nothing.\n- For bug fixes, add a failing test first, then fix.\n- Every feature ships with meaningful tests. A `.skip` is a coverage hole: fix it or delete it.\n- Use `node:test` and `node:assert/strict` for new tests and Mocha migrations. Plugin tests use `haraka-test-fixtures` (`makePlugin`, `makeConnection`, `callHook`).\n- Run the package's `lint`, `prettier`, and `format` before handoff.\n\n## Commands (run inside the target package)\n\n- Test: `npm test`. Single file: `node --test test/path/to/file.js`.\n- Haraka core repo only: `./run_tests [test/plugins/foo.js]`.\n- Coverage: `npm run test:coverage`; lcov: `npm run test:coverage:lcov`. Keep coverage at/above ~90%.\n- If coverage output includes non-source files (e.g. `package.json`, `test/*`), scope it with `--test-coverage-include` (preferred when the list is short) or `--test-coverage-exclude`.\n- Lint/format: `npm run lint` / `prettier` / `format`. Version drift: `npm run versions[:fix]`.\n\n## Package script parity\n\n- node:test packages should expose `test`, `test:coverage`, `test:coverage:lcov`, `lint`, `prettier`, `format` with matching shapes across siblings. Standardize on node:test coverage (not c8); add the canonical scripts when touching a package that lacks them:\n  ```jsonc\n  \"test:coverage\": \"node --test --experimental-test-coverage\",\n  \"test:coverage:lcov\": \"mkdir -p coverage && node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info\"\n  ```\n\n## Repo badges\n\n- Code climate is deprecated. Update with qlty.sh instead.\n- The canonical format for badges should be:\n  - Top of README.md:\n    - [![Test][ci-img]][ci-url] [![Cover][cov-img]][cov-url] [![Qlty][qlty-img]][qlty-url]\n  - Bottom of README.md:\n    - [ci-img]: https://github.com/haraka/<name>/actions/workflows/ci.yml/badge.svg\n    - [ci-url]: https://github.com/haraka/<name>/actions/workflows/ci.yml\n    - [cov-img]: https://codecov.io/github/haraka/<name>/coverage.svg\n    - [cov-url]: https://codecov.io/github/haraka/<name>\n    - [qlty-img]: https://qlty.sh/gh/haraka/projects/<name>/maintainability.svg\n    - [qlty-url]: https://qlty.sh/gh/haraka/projects/<name>\n","CLAUDE.md":"see AGENTS.md\n","docs/plugins/aliases.md":"# aliases\n\nRepackaged as [haraka-plugin-aliases](https://github.com/haraka/haraka-plugin-aliases).\n","docs/plugins/auth/auth_bridge.md":"# auth/auth_bridge\n\nThis plugin allows you to authenticate users to remote SMTP servers\nbridging the original user and password to the remote server,\nand proxy the result back to authenticate the client.\n\nThis plugin is meant to be used with the plugin `queue/smtp_bridge`.\n\nIt is different than `auth/auth_proxy` because it doesn't require\nthe AUTH user in user@domain.com format, and it doesn't check that\nthe domain is the configuration file. This plugins simply takes\nthe original user and password and tries to authenticate it in the\nremote SMTP server.\n\n## Configuration\n\nConfiguration is stored in `config/smtp_bridge.ini` and uses the INI\nstyle formatting.\n\nThe configuration of this plugin is simple:\n\n    host=localhost\n    #port=\n    #auth_type=\n    #priority=10\n\n- host: the host where you will be authenticating and posting,\n  for example `smtp.host.tld`, `192.0.2.25:587`, or `[2001:db8::1]:587`.\n  This is the only setting required. See [Endpoint][endpoint] for the\n  accepted forms. An IPv6 address must be bracketed to include a port.\n\nIf needed you can also set\n\n- port: used when `host` doesn't include one. Defaults to 25.\n\n[endpoint]: https://github.com/haraka/haraka-net-utils#endpoint\n\nThe options `auth_type` and `priority` will be used by `queue/smtp_bridge`\n","docs/plugins/auth/auth_ldap.md":"# auth/auth_ldap\n\nRepackaged as [haraka-plugin-auth-ldap](https://github.com/haraka/haraka-plugin-auth-ldap).\nLoading `auth/auth_ldap` in `config/plugins` is auto-redirected to `auth-ldap`.\n","docs/plugins/auth/auth_proxy.md":"# auth/auth_proxy\n\nThis plugin allows you to authenticate users by domain to remote SMTP servers\nand proxy the result back to authenticate the client.\n\nFor this to work - the AUTH username _must_ be in user@domain.com format\nregardless as to whether the remote SMTP server requires it in this format.\nThe domain part of the username is used to look-up which SMTP servers should\nbe used to authenticate users for that domain.\nWhen sending the AUTH credentials to the remote server, this plugin will try\nand send the full username e.g. user@domain.com first and if this fails it\nwill then strip the @domain.com part and just send the unqualified username.\n\nDue to the way this plugin works - it can only support PLAIN and LOGIN\nauthentication methods and for this reason it requires that STARTTLS be\nused via the tls plugin before it will advertise AUTH capabilities by the\nEHLO command. When connecting to the remote SMTP systems it will always\nattempt STARTTLS if it is offered, but it does _not_ require it, so caution\nshould be exercised.\n\n## Configuration\n\nConfiguration is stored in `config/auth_proxy.ini` and uses the INI\nstyle formatting.\n\nThe configuration of this plugin is simple:\n\n    [domains]\n    domain.com = server1.domain.com:587 server2.domain.com\n\nWhere domain.com is the domain-part of the username equals a list of hosts\nthat should be consulted in host:port format. The :port is optional and will\ndefault to 25. The list of hosts can be space, semi-colon or comma separated.\n\nIf more than host is specified, then subsequent hosts will only be tested if\nthere is some sort of error e.g. timeout, connection or protocol error.\n","docs/plugins/auth/auth_vpopmaild.md":"# auth/auth_vpopmaild\n\nThe `auth/vpopmaild` plugin allows SMTP users to authenticate against a vpopmaild daemon.\n\n## Configuration\n\nThe configuration file is stored in `config/auth_vpopmaild.ini`.\n\n### settings\n\n- host: The host/IP that vpopmaild is listening on (default: localhost).\n\n- port: The TCP port that vpopmaild is listening on (default: 89).\n\n- sysadmin: A colon separated username:password of a vpopmail user with SYSADMIN privileges (see vpopmail/bin/vmoduser -S). This is **only** necessary to support CRAM-MD5 which requires access to the clear text password. On new installs, it's best not to use CRAM-MD5, as it requires storing clear text passwords. Legacy clients with MUAs configured to authenticate with CRAM-MD5 will need this enabled.\n\n- constrain_sender: (default: true). For outbound messages (due to successful AUTH), constrain the envelope sender (MAIL FROM) to the same domain as the authenticated user. This setting, combined with `rate_rcpt_sender` in the [limit](https://github.com/haraka/haraka-plugin-limit) plugin can dramatically reduce the amount of backscatter and spam sent when an email account is compromised.\n\n### Per-domain Configuration\n\nAdditionally, domains can each have their own configuration for connecting\nto vpopmaild. The defaults are the same, so only the differences needs to\nbe declared. Example:\n\n```ini\n[example.com]\nhost=192.168.0.1\nport=999\n\n[example2.com]\nhost=192.168.0.2\nsysadmin=postmaster@example2.com:sekret\n```\n","docs/plugins/auth/flat_file.md":"# auth/flat_file\n\nThe `auth/flat_file` plugin allows you to create a file containing username and password combinations, and have relaying users authenticate from that file.\n\nNote that passwords are stored in clear-text, so this may not be a great idea for large scale systems. However the plugin would be a good start for someone looking to implement authentication using some other form of auth.\n\n**Security** - it is recommended to switch to [auth-encfile][url-authencflat] to protect your user credentials.\n\n**IMPORANT NOTE** - this plugin requires that STARTTLS be used via the tls plugin before it will advertise AUTH capabilities by the EHLO command. Localhost and IPs in RFC1918 ranges\nare exempt from this rule.\n\n## Configuration\n\nConfiguration is stored in `config/auth_flat_file.ini`.\n\n- [core]methods\n\nAuthentication methods are listed in the `[core]methods` parameter. Authentification methods are comma separated. Currently supported methods are: `CRAM-MD5`, `PLAIN` and `LOGIN`. The `PLAIN` and `LOGIN` methods are insecure and require TLS to be enabled.\n\n- [core]constrain_sender: (default: true). For outbound messages (due to successful AUTH), constrain the envelope sender (MAIL FROM) to the same domain as the authenticated user. This setting, combined with `rate_rcpt_sender` in the [limit](https://github.com/haraka/haraka-plugin-limit) plugin can dramatically reduce the amount of backscatter and spam sent when an email account is compromised.\n\nExample:\n\n```ini\n[core]\nmethods=PLAIN,LOGIN,CRAM-MD5\nconstrain_sender=true\n```\n\nUsers are stored in the `[users]` section.\n\nExample:\n\n```ini\n[users]\nuser1=password1\nuser@domain.com=password2\n```\n\n[url-authencflat]: https://github.com/AuspeXeu/haraka-plugin-auth-enc-file\n","docs/plugins/block_me.md":"# block_me\n\nThis plugin allows you to configure an address which mail sent to will be\nparsed for a From: address in the body of the message, and will add that\nfrom address to the `mail_from.blocklist` config file.\n\nEffectively this allows your users to forward spams that got through to a\nparticular mailbox to block them in the future.\n\nNote that this is a system-wide block, and not per-user. Be careful with this.\n\n## Configuration\n\n- `config/block_me.recipient` - a file containing the address to email to\n  get something blocked. For example: **spam@domain.com**.\n\n- `config/block_me.senders` - a file containing a list of email addresses\n  that are allowed to email the dropbox.\n","docs/plugins/data.signatures.md":"# data.signatures\n\nThis plugin allows you to add string signatures to a configuration file and\nhave this plugin scan the body text of an email for those strings. Mails\nmatching these signatures will be blocked.\n\n## Configuration\n\n- data.signatures\n\n  This file contains a list of strings (one per line) that will be matched.\n","docs/plugins/delay_deny.md":"# delay_deny\n\nDelays all pre-DATA 'deny' results until the recipients are sent\nand all post-DATA commands until all hook_data_post plugins have run.\nThis allows relays and authenticated users to bypass pre-DATA rejections.\n\n## Configuration\n\nConfiguration options are in config/delay_deny.ini.\n\nThis plugin operates in one of two modes: included and excluded.\n\n### included plugins\n\nA comma or semicolon separated list of denials that are to be included.\nIn this mode, _only_ plugins in the list are bypassed. All other plugins\ncan immediately reject connections.\n\n### excluded plugins\n\nA comma or semicolon separated list of denials that are to be excluded.\nExcluded plugins that are not bypassed and can still immediately reject\nconnections.\n","docs/plugins/max_unrecognized_commands.md":"# max_unrecognized_commands\n\nThe functionality of this plugin was folded into\n[haraka-plugin-limit](https://github.com/haraka/haraka-plugin-limit).\nLoading `max_unrecognized_commands` in `config/plugins` is auto-redirected\nto `limit`.\n","docs/plugins/prevent_credential_leaks.md":"# prevent_credential_leaks\n\nThis plugin prevents an authenticated user (via SMTP AUTH) from sending\ntheir username and password out in a message (e.g. like replying to a\nphish).\n\nIf their username and password are detected inside the message body, then\nthe message is rejected with the message:\n\n```\nCredential leak detected: never give out your username/password to anyone!\n```\n\nNote that if the username is qualified e.g. user@domain.com - then the\nplugin will search for both `user` and `user@domain.com` for maximum\neffectiveness.\n\n## Configuration\n\nNo configuration is required. Simply add the plugin to your `config/plugins`\nfile. It should be added before any other plugins that run on hook_data_post\nfor maximum efficiency.\n","docs/plugins/process_title.md":"# process_title\n\nThis plugin causes the process title seen by the UNIX 'ps' command to\nbe modified from this:\n\n```\nnode haraka.js -c /etc/haraka\n```\n\nto this:\n\n```\nHaraka (master) cn=11148 cc=1082 cps=21/25.24/79 rcpts=144950/1.84 rps=518/328.18/586 msgs=78815/7.07 mps=302/178.44/329 out=0/0/0 respawn=0\n \\_ Haraka (worker) cn=1646 cc=140 cps=5/3.73/17 rcpts=20310/1.86 rps=75/46.04/102 msgs=10938/6.65 mps=42/24.8/56 out=0/0/0\n \\_ Haraka (worker) cn=1563 cc=168 cps=3/3.54/18 rcpts=19844/1.87 rps=78/45/96 msgs=10627/6.8 mps=49/24.1/53 out=0/0/0\n \\_ Haraka (worker) cn=1852 cc=172 cps=3/4.2/16 rcpts=26278/2.03 rps=93/59.56/114 msgs=12938/6.99 mps=40/29.33/65 out=0/0/0\n \\_ Haraka (worker) cn=1704 cc=187 cps=5/3.86/14 rcpts=23688/1.84 rps=93/53.7/125 msgs=12886/7.56 mps=64/29.21/66 out=0/0/0\n \\_ Haraka (worker) cn=2296 cc=218 cps=2/5.2/20 rcpts=29300/1.78 rps=117/66.4/125 msgs=16489/7.18 mps=40/37.37/66 out=0/0/0\n \\_ Haraka (worker) cn=2091 cc=195 cps=4/4.74/16 rcpts=25646/1.71 rps=84/58.12/117 msgs=14982/7.16 mps=52/33.95/66 out=0/0/0\n```\n\nwhere:\n\n- cn = Total number of connections\n- cc = Total number of concurrent connections\n- cps = Number of connections in the last second / average / maximum\n- rcpts = Total number of recipients / Average number of recipients per message\n- rps = Number of recipients in the last second / average / maximum\n- msgs = Total number of messages / Average number messages per connection\n- mps = Number of messages in the last second / average / maximum\n- out = Mails being processed / Mails waiting to be processed / Mails in temp fail state\n- respawn = Number of worker processes respawned (only under cluster)\n\nIf 'cluster' is used then the master process will show the total\nacross all workers, with the exception of outbound stats.\n\nAll of the counts shown are since the process started, so if a\nworker has been re-started then the counts may not add up.\n\nNote: this plugin should be added at the top of `config/plugins` so\nthat its `connect_init`, `rcpt`, `data`, and `disconnect` hooks run\nbefore any plugin that might short-circuit those hooks.\n","docs/plugins/queue/deliver.md":"# queue/deliver\n\nThis plugin is now redundant. Outbound delivery is now built into Haraka.\n","docs/plugins/queue/discard.md":"# discard\n\nThis plugin will discard a message by pretending that the message was queued.\n\nIt is designed to be used by other plugins which request the message be\ndiscard by setting a connection or transaction note that this plugin\nchecks.\n\nIt uses the 'queue' hook, so it runs after all the plugins that hook on `data_post`.\n\nIf you use the 'quarantine' plug-in then this plugin should run _after_ it.\n\nUSE THIS PLUGIN WITH CARE!\n\n# Enable\n\nEnable by adding a `queue/discard` entry in `config/plugins` **before** your\nother queue plugins that perform actual deliveries.\n\n# Usage\n\nSet\n\n```javascript\nconnection.notes.discard = [1 | true]\n```\n\nor\n\n```javascript\nconnection.transaction.notes.discard = [1 | true]\n```\n","docs/plugins/queue/lmtp.md":"# queue/lmtp\n\nThis plugin delivers inbound mail via LMTP.\n\n## Configuration\n\nLMTP is enabled by adding `queue/lmtp` to config/plugins. LMTP delivery is configured in `config/lmtp.ini` . By default, all inbound messages are forwarded to the host specified in the `[main]` section. Domain specific routes can be specified by creating additional sections with the same host/port or path options.\n\n### lmtp.ini\n\n```ini\n; defaults\nhost=localhost\nport=24\n\n[example1.com]\n; Goes elsewhere\nhost=10.1.1.1\nport=2400\n\n[example2.com]\n; Using unix domain sockets\npath = /tmp/blah_com_socket\n```\n","docs/plugins/queue/qmail-queue.md":"# queue/qmail-queue\n\nThis plugin delivers the mail to the `qmail-queue` program, which can be used\nfor both inbound and outbound delivery.\n\n## Configuration\n\n- qmail-queue.path\n\n  The path to the `qmail-queue` binary. Default: `/var/qmail/bin/qmail-queue`\n\n- qmail-queue.ini\n  - enable_outbound=true\n\n    Deliver outbound email to qmail. Set to false to use Haraka's\n    separate Outbound mail routing (MX based delivery)).\n","docs/plugins/queue/quarantine.md":"# quarantine\n\nThis plugin will save a message (in message/rfc822 format) to a specified\ndirectory, which will be created automatically if it does not already exist,\na dated sub-folder is also added to the end of the path specified in YYYYMMDD\nformat.\n\nIt is designed to be used by other plugins which request the message be\nquarantined by setting a connection or transaction note that this plugin\nchecks.\n\nNOTE: this plugin simply saves a copy of the message. It does not reject or\ndiscard the message and relies on another plugin to perform this function.\n\nIt uses the 'queue' hook, so that it runs after all the 'data_post' plugins\nand should be listed in 'config/plugins' to run before your queue hooks that\nperform actual deliveries.\n\nTo ensure that only completely written files are present in the quarantine,\nthe files are written to a temporary directory first and then hardlinked to\nthe final destination before the temporary file is deleted.\n\nThe temporary directory is 'quarantine_path/tmp' which defaults to:\n/var/spool/haraka/quarantine/tmp.\n\nUpon start-up, any files present in the temporary directory are deleted\nsyncronously prior to any messages being accepted.\n\n## Configuration\n\nThis plugin looks for 'quarantine.ini' in the config directory.\n\n- quarantine_path (default: /var/spool/haraka/quarantine)\n\n  The default base path to save the quarantine files to. It will be created\n  if it does not already exist.\n\n## Usage\n\nIf you wish to keep a copy of the message in your plugin, simply either:\n\n```javascript\nconnection.notes.quarantine = [1 | true | 'sub/directory/path']\n```\n\nor\n\n```javascript\nconnection.transaction.notes.quarantine = [1 | true | 'sub/directory/path']\n```\n\ne.g.\n\n```javascript\nconnection.notes.quarantine = 1\n```\n\nwould save the message to '/var/spool/quarantine/haraka/YYYYMMDD/UUID' where\nYYYMMDD and UUID are expanded to current date and transaction UUID.\n\nand\n\n```javascript\nconnection.notes.quarantine = 'corpus'\n```\n\nwould save the message to '/var/spool/quarantine/haraka/corpus/YYYYMMDD/UUID'.\n\nNote: you can specify 'corpus/foo' or 'corpus/foo/bar' and the directories will\nbe automatically created. Do not add any leading or trailing slashes.\n\nBy default - after the message is quarantined, the plugin will tell Haraka to\ncontinue to the next plugin. You can specify a different action like DENY or\nOK and supply an optional message using the following notes:\n\n```javascript\nconnection.notes.quarantine_action = [OK, 'Message quarantined']\nconnection.transaction.notes.quarantine_action = [DENY, 'Message rejected']\n```\n\nIf you don't want to supply a specific message back to the client you can\nalso just specify a return code:\n\n```javascript\nconnection.notes.quarantine_action = OK\nconnection.transaction.notes.quarantine_action = DENY\n```\n","docs/plugins/queue/smtp_bridge.md":"# queue/smtp_bridge\n\nThis plugin delivers to another SMTP server, bridging the authentication\ndetails and post data from the initial connection.\n\nThis plugin is meant to be used with the plugin `auth/auth_bridge`.\n\nIt is different than `queue/smtp_proxy` or `queue/smpt_forward` because\nit doesn't use the AUTH details from a configuration file. This plugins\nsimply post the data from the original connection to the remote SMTP server\nusing the original AUTH details.\n\n## Configuration\n\nConfiguration is stored in `config/smtp_bridge.ini` and uses the INI\nstyle formatting.\n\nThe configuration of this plugin is simple:\n\n    host=localhost\n    #port=\n    #auth_type=\n    #priority=10\n\n- host: the host where you will be authenticating and posting,\n  for example `smtp.host.tld`. This is the only setting required.\n\nIf needed you can also set\n\n- port: default to empty and Haraka will use 25.\n- auth_type: default to empty and Haraka will try to pick an appropriate method.\n- priority: default to 10.\n","docs/plugins/queue/smtp_forward.md":"# queue/smtp_forward\n\n==================\n\nThis plugin delivers to another mail server. This is a common setup when you want to have a mail server with a solid pedigree of outbound delivery to other hosts, and inbound delivery to users.\n\nIn comparison to `queue/smtp_proxy`, this plugin waits until queue time to attempt the ongoing connection. This can be a benefit in reducing connections to your inbound mail server when you have content filtering (such as spamassassin) enabled. A possible downside is that it also delays recipient validation that the ongoing mail server may provide until queue time.\n\n## Configuration\n\n---\n\nConfiguration is stored in smtp_forward.ini in the following keys:\n\n- enable_outbound=[true]\n\n  SMTP forward outbound messages (set to false to enable Haraka's separate Outbound mail routing (MX based delivery)).\n\n- host=HOST\n\n  The host to connect to.\n\n- port=PORT\n\n  The port to connect to. Default: 25\n\n- connect_timeout=SECONDS\n\n  The maximum amount of time to wait when creating a new connection to the host. Default: 30 seconds.\n\n- timeout=SECONDS\n\n  The amount of seconds to let a backend connection live idle in the connection pool. This should always be less than the global plugin timeout, which should in turn be less than the connection timeout.\n\n- max_connections=NUMBER\n\n  Maximum number of connections at any given time. Default: 1000\n\n- enable_tls=[true]\n\n  Enable opportunistic TLS with the forward host via `STARTTLS` (if the host advertises it). This plugin does not work with implicit SMTP over TLS.\n\n- auth_type=[plain\\|login]\n\n  Enable PLAIN or LOGIN SMTP AUTH. This is required to enable AUTH.\n\n- auth_user=USERNAME\n\n  SMTP AUTH username to use.\n\n- auth_pass=PASSWORD\n\n  SMTP AUTH password to use.\n\n- queue\n\n  Which queue plugin to use. Default: undefined. The default bahavior is to use smtp_forward for inbound connections and outbound for relaying connections. This option is used for complex mail routes.\n\n- check_sender=false\n\n  Requires that sender domains defined in smtp_forward.ini (see Per-Domain below) have relaying privileges. This is a form of spoof prevention and assumes that any mail clients have relaying or AUTH privileges. This is usually the case.\n\n- check_recipient=false\n\n  By default, Haraka accepts no emails until a recipient plugin has been configured to accept mails for a domain. The simplest common case is the in_host_list plugin with a list of domains in config/host_list. An alternative is to set `check_recipient=true` and list each domain in a definition block in smtp_forward.ini (see Per-Domain Configuration). An example for two domains:\n\n  [example.com]\n  [example.net]\n\n- [tls]\n\nClient STARTTLS options are assembled by merging:\n\n1. `tls.ini` `[main]` — the global Haraka TLS config\n2. `smtp_forward.ini` `[tls]` — overrides. Anything set here wins.\n\nExample `smtp_forward.ini` `[tls]` section:\n\n    [tls]\n    rejectUnauthorized=true\n    minVersion=TLSv1.2\n    no_tls_hosts[]=10.0.0.5\n\nPer-domain `enable_tls=false` still disables STARTTLS for that backend. Per-domain TLS cipher/cert overrides are not currently supported.\n\nChanges to `tls.ini` require a Haraka restart to apply to the forward path; changes to `smtp_forward.ini` are picked up by the existing reload hook.\n\n# Per-Domain Configuration\n\nMore specific forward routes for domains can be defined. The domain is chosen based on the value of the `domain_selector` config variable.\n\nWhen `domain_selector` is set to `rcpt_to` (the default), more specific routes are only honored for SMTP connections with a single recipient or SMTP connections where every recipient host is identical.\n\nWhen `domain_selector` is set to `mail_from`, it first searches for configuration using the complete email address, falls back to the domain if not found, then to main configuration.\n\nenable_outbound can be set or unset on a per-domain level to enable or disable forwarding for specific domains.\n\n    # default SMTP host\n    host=1.2.3.4\n    # auth_type=plain\n    # auth_user=user\n    # auth_user=pass\n\n    [example1.com]\n    host=1.2.3.5\n    # auth_type=plain\n    # auth_user=user\n    # auth_pass=pass\n\n    [email@example1.com]\n    host=1.2.3.5\n    # auth_type=plain\n    # auth_user=user\n    # auth_pass=pass\n\n    [example2.com]\n    host=1.2.3.5\n\n    [example3.com]\n    host=1.2.3.6\n\n    [example4.com]\n    enable\\_outbound=false\n\n# Split host forward routing\n\nWhen an incoming email transaction has multiple recipients with different forward routes, recipients to subsequent forward routes are deferred. Example: an incoming email transaction has recipients user@example1.com, user@example2.com, and user@example3.com. The first two recipients will be accepted (they share the same forward destination) and the latter will be deferred. It will arrive in a future delivery attempt by the remote.\n","docs/plugins/queue/smtp_proxy.md":"# queue/smtp_proxy\n\n================\n\nThis plugin delivers to another mail server. This is a common setup when you want to have a mail server with a solid pedigree of outbound delivery to other hosts, and inbound delivery to users.\n\nIn comparison to `queue/smtp_forward`, this plugin makes a connection at MAIL FROM time to the ongoing SMTP server. This can be a benefit in that you get any SMTP-time filtering that the ongoing server provides, in particular one important facility to some setups is recipient filtering.\n\nBe aware that other than connect and HELO-time filtering, you will have as many connections to your ongoing SMTP server as you have to Haraka.\n\n## Configuration\n\n---\n\nConfiguration is stored in smtp_proxy.ini in the following keys:\n\n- enable_outbound=[true]\n\n  SMTP proxy outbound messages (set to false to enable Haraka's\n  separate Outbound mail routing (MX based delivery)).\n\n- host=HOST\n\n  The host to connect to.\n\n- port=PORT\n\n  The port to connect to.\n\n- connect_timeout=SECONDS\n\n  The maximum amount of time to wait when creating a new connection\n  to the host. Default if unspecified is 30 seconds.\n\n- timeout=SECONDS\n\n  The amount of seconds to let a backend connection live idle in the\n  proxy pool. This should always be less than the global plugin timeout,\n  which should in turn be less than the connection timeout.\n\n- max_connections=NUMBER\n\n  Maximum number of connections to create at any given time.\n\n- enable_tls=[true|yes|1]\n\n  Enable opportunistic TLS with the forward host via `STARTTLS` (if the host advertises it).\n\n- auth_type=[plain|login]\n\n  Enable PLAIN or LOGIN SMTP AUTH. This is required to enable AUTH.\n\n- auth_user=USERNAME\n\n  SMTP AUTH username to use.\n\n- auth_pass=PASSWORD\n\n  SMTP AUTH password to use.\n\n- [tls]\n\nClient STARTTLS options are assembled by merging:\n\n1. `tls.ini` `[main]` — the global Haraka TLS config.\n2. `smtp_proxy.ini` `[tls]` — overrides. Anything set here wins.\n\nChanges to `tls.ini` require a Haraka restart to apply to the proxy path; changes to `smtp_proxy.ini` are picked up by the existing reload hook.\n","docs/plugins/queue/test.md":"# queue/test\n\nThis plugin saves incoming E-Mail to your temporary directory, as `mail_{message_id}.eml`, where message_id is a UUID.\n\nThis plugin can be useful to quickly test if you're able to receive incoming E-Mail and just dump them to disk.\n\nThe temporary directory is determined using Node's [`os.tmpdir()`](https://nodejs.org/api/os.html#ostmpdir), which respects standard platform configurations.\n","docs/plugins/rcpt_to.in_host_list.md":"# rcpt_to.in_host_list\n\nThis plugin is the mainstay of an inbound Haraka server. It should list the\ndomains that are local to the host. Mails that have RCPT TO not matching\na host in the given list will be passed onto other rcpt hooks. If no rcpt\nhook accepts the connection, it will be rejected.\n\n## Configuration\n\n- host_list\n\n  Specifies the list of hosts that are local to this server.\n\n- host_list_regex\n\n  Specifies the list of regexes that are local to this server. Note\n  all these regexes are anchored with ^regex$. One can choose not to\n  anchor with .\\*. There is the potential for bad regexes to be\n  too permissive if we don't anchor.\n\n- host_list.anti_spoof\n\n  When enabled, this will cause Haraka to reject any MAIL FROM where\n  the host appears within the host list but the connected host is not\n  a relay, e.g. connection.relaying is not set either by SMTP AUTH or\n  another plugin like 'relay'.\n\n## Relaying\n\nThis plugin checks to see if the MAIL FROM domain is local. When\nconnection.relaying is detected (haraka -h relay) and the MAIL FROM domain is\nlocal, this plugin will vouch for any RCPT. This limits relaying users to\nsending from local domains, which is much safer than letting relay clients\nsend from any domain.\n","docs/plugins/rcpt_to.max_count.md":"# rcpt_to.max_count\n\nThe functionality of this plugin was integrated in to [haraka-plugin-limit](https://github.com/haraka/haraka-plugin-limit).\n","docs/plugins/record_envelope_addresses.md":"# record_envelope_addresses\n\nThis plugin adds two new header lines.\n\n- X-Envelope-To: the envelope RCPT TO address\n- X-Envelope-From: the envelope MAIL FROM address\n\nIt is useful if you need to know the exact addresses used to send an email, e.g. when\nthe email was sent to you as BCC or if it is a newsletter. In both cases the recipient\naddress is normally not recorded in the headers.\n\n## Caveats\n\nIf you enable this plugin you may introduce a possible information leak, i.e. disclosure\nof BCC recipients. So you never want to use this on an outgoing mail server and maybe also\nnot if this server is used as a relay.\n\n## Configuration\n\nThis plugin has no configuration.\n","docs/plugins/relay.md":"# relay\n\nRepackaged as [haraka-plugin-relay](https://github.com/haraka/haraka-plugin-relay).\n","docs/plugins/reseed_rng.md":"# reseed_rng\n\nReseeds `Math.random()` in each cluster worker at start-up using\n`crypto.randomBytes(256)`. Without this, workers forked at nearly the\nsame time can end up with correlated PRNG state, which can produce\nUUID collisions and other \"this should be impossible\" bugs.\n\nThe plugin relies on [seedrandom](https://www.npmjs.com/package/seedrandom)\nbeing loaded so that `Math.seedrandom()` is available.\n\nAnyone running with `nodes=...` in `smtp.ini` (i.e. cluster mode) should\nconsider enabling this plugin.\n\n## Configuration\n\nNo configuration.\n","docs/plugins/status.md":"# Status\n\nThis plugin allows to get internal status of queues and pools with SMTP commands sent from localhost.\n\n## Communication\n\n- **Request** &rarr; `STATUS <CMD> [param1] [param2]....`\n- **Response** &larr; _&lt;SMTP code 211 or 500>&lt;space>&lt;json encoded response>\\r\\n_\n\n### Example\n\n```\n< 220 example.com ESMTP Haraka ready\n> STATUS QUEUE INSPECT\n< 211 {\"delivery_queue\":[],\"temp_fail_queue\":[]}\n```\n\n## Available commands list\n\n- `STATUS POOL LIST` - map of active outbound connection pools, keyed by `host:port`\n- `STATUS QUEUE STATS` - queue statistics in format `\"<in_progress>/<delivery_queue length>/<temp_fail_queue length>\"`\n- `STATUS QUEUE LIST` - list of queue files on disk with _uuid, domain, mail_from, rcpt_to_ attributes\n- `STATUS QUEUE INSPECT` - returns merged content of `outbound.delivery_queue` and `outbound.temp_fail_queue` across all workers\n- `STATUS QUEUE DISCARD file` - stop delivering email file\n- `STATUS QUEUE PUSH file` - try to re-deliver email immediately\n\n## Notes\n\n### Live data only\n\n`POOL LIST`, `QUEUE STATS`, and `QUEUE INSPECT` reflect live in-memory state. They show only messages currently being processed or waiting in the retry queue. `QUEUE LIST` reads queue files from disk and may show messages that have already been delivered if they haven't been cleaned up yet.\n\n### Cluster mode\n\nIn cluster mode, `POOL LIST`, `QUEUE STATS`, and `QUEUE INSPECT` aggregate results from all worker processes into a single response:\n\n- `POOL LIST` — pool maps from all workers are merged into one object\n- `QUEUE STATS` — counters from all workers are summed into a single `\"N/N/N\"` string\n- `QUEUE INSPECT` — `delivery_queue` and `temp_fail_queue` arrays from all workers are concatenated\n\n`QUEUE LIST` always runs on the master process since it reads shared queue files from disk.\n","docs/plugins/tarpit.md":"# tarpit\n\nThis plugin is designed to introduce deliberate delays on the response\nof every hook in order to slow down a connection. It has no\nconfiguration and is designed to be used only by other plugins.\n\nIt must be loaded early in config/plugins (e.g. before any plugins\nthat accept recipients or that return OK) but must be loaded _after_\nany plugins that wish to use it.\n\n## Usage\n\nTo use this plugin in another plugin set:\n\n    connection.notes.tarpit = <seconds to delay>;\n\nor\n\n    connection.transaction.notes.tarpit = <seconds to delay>;\n\n## Configuration\n\nThe configuration file for tarpit is config/tarpit.ini.\n\n- hooks_to_delay - a list of hooks to delay at. This setting can be used to\n  override the default list in the plugin. For example, if you notice that\n  malware is disconnecting after delaying rcpt_ok, you can remove just that\n  hook from the list:\n\nhooks_to_delay=connect,helo,ehlo,mail,rcpt,data,data_post,queue,unrecognized_command,vrfy,noop,rset,quit\n\n## Plugin Timeout\n\nconfig/tarpit.timeout (Default: 0)\n\nAll Haraka plugins can configure a _name_.timeout file. The timeout specifies\nhow long Haraka lets the plugin do nothing before it times out. When zero,\nthere is no timeout. When non-zero and _seconds to delay_ is longer than\ntarpit.timeout (default: 1s), you'll get errors like this in your log files:\n\n    [core] Plugin tarpit timed out on hook rcpt - make sure it calls the callback\n    [core] Plugin tarpit timed out on hook quit - make sure it calls the callback\n\nThe solution is to set the contents of config/tarpit.timeout to zero or\n**seconds to delay** + 1.\n\n## Logging\n\nWhen tarpitting a command it will log 'tarpitting response for Ns' to\nthe INFO facility where N is the number of seconds.\n","docs/plugins/tls.md":"# tls\n\nThis plugin enables the use of TLS (via `STARTTLS`) in Haraka.\n\nFor this plugin to work you must have SSL certificates installed correctly.\n\nHaraka has [SNI](https://en.wikipedia.org/wiki/Server_Name_Indication) support. When the remote MUA/MTA presents a servername during the TLS handshake and a TLS certificate with that Common Name matches, that certificate will be presented. If no match is found, the default certificate (see Certificate Files) is presented.\n\n## Certificate Files\n\nDefaults settings are shown and can be overridden in `config/tls.ini`.\n\n```ini\nkey=tls_key.pem\ncert=tls_cert.pem\ndhparam=dhparams.pem\n```\n\n## Certificate Directory\n\nIf the directory `config/tls` exists, files within the directory are PEM encoded TLS files in one of two formats: bundles or Wild Wild West.\n\n### Certificate bundles\n\nGenerate PEM bundles in The Usual Way[TM] by concatenating the key, certificate, and CA/chain certs in that order. Example:\n\n```sh\ncat example.com.key example.com.crt ca-int.crt > haraka/config/tls/example.com.pem\n```\n\nAn example [acme.sh](https://acme.sh) deployment [script](https://github.com/msimerson/Mail-Toaster-6/blob/master/provision/letsencrypt.sh) installs [Let's Encrypt](https://letsencrypt.org) certificate bundles to the Haraka `config/tls`directory.\n\n### Wild Wild West\n\nPEM encoded TLS certificates and keys can be stored in files in `config/tls`. The certificate loader is recursive, so TLS files can be in subdirs like `config/tls/mx1.example.com`. The certificate names are parsed from the 1st cert in each file and indexed by the certs Common Name(s). Subject Alternate Names are supported. The file name containing the certificates does _not_ matter. Additional certificates within each file are presumed to be CA chain (intermediate) certificates.\n\nIf the TLS key is stored in the same file as the matching certificate, then the name of the file does not matter. If the TLS key is alone in a file, the file MUST be named with the keys Common Name. The file extension does not matter, `.pem` and `.key` are common. If the key is used for multiple CNs, the key must be stored in a file name matching each CN. Examples of working TLS key/cert file pairs for the Common Name mx1.example.com:\n\n1. certificate bundle (see above), key & cert in same file\n   - config/tls/mx1.example.com.pem (recommended)\n   - config/tls/any-unique-name.pem (CN is extracted from 1st cert)\n2. files in TLS dir\n   - config/tls/mx1.example.com.crt\n   - config/tls/mx1.example.com.key\n3. files in subdir\n   - config/tls/example.com/mx1.cert\n   - config/tls/example.com/mx1.example.com.key\n4. wildcard bundle on Windows platform (\\* is not allowed in file names)\n   - config/tls/\\_.example.com.pem\n\n## Purchased Certificate\n\nFor purchased certificate, append any intermediate/chained/ca-cert files to the certificate in this order:\n\n1. The CA signed SSL cert\n2. Any intermediate certificates\n3. The CA root certificate\n\nSee also [Setting Up TLS](https://github.com/haraka/Haraka/wiki/Setting-up-TLS-with-CA-certificates)\n\n## Self Issued (unsigned) Certificate\n\nCreate a certificate and key file in the config directory with the following command:\n\n    openssl req -x509 -nodes -days 2190 -newkey rsa:2048 \\\n            -keyout config/tls_key.pem -out config/tls_cert.pem\n\nYou will be prompted to provide details of your organization. Make sure the\nCommon Name is set to your servers Fully Qualified Domain Name, which should\nbe the same as the contents of your `config/me` file.\n\n## Configuration\n\nThe following settings can be specified in `config/tls.ini`.\n\n### key\n\nSpecifies an alternative location for the key file. For multiple keys, use `key[]=` assignment for each. Non-absolute paths are relative to the `config/` directory.\n\nTo configure a single key and a cert chain, located in the `config/`\ndirectory, use the following in `tls.ini`:\n\n```ini\nkey=example.com.key.pem\ncert=example.com.crt-chain.pem\n```\n\nTo use multiple pairs of key and cert chain files outside of the haraka\n`config/` directory, configure instead:\n\n```ini\nkey[]=/etc/ssl/private/example.com.rsa.key.pem\ncert[]=/etc/ssl/private/example.com.rsa.crt-chain.pem\nkey[]=/etc/ssl/private/example.com.ecdsa.key.pem\ncert[]=/etc/ssl/private/example.com.ecdsa.crt-chain.pem\n```\n\n### cert\n\nSpecifies the location(s) for the certificate chain file. For multiple certificate chains, use `cert[]=` assignment for each. Non-absolute paths are relative to the `config/` directory. See the description of the `key` parameter for specific use.\n\n### no_tls_hosts\n\nIf needed, add this section to the `config/tls.ini` file and list any IP ranges that have broken TLS hosts. Ex:\n\n```ini\n[no_tls_hosts]\n192.168.1.3\n172.16.0.0/16\n```\n\nNote: `[no_tls_hosts]` section applies to inbound only. For outbound mail, this feature is implemented as an array like `force_tls_hosts`:\n\n```ini\n[outbound]\nno_tls_hosts[]=192.168.1.3\nno_tls_hosts[]=172.16.0.0/16\n```\n\nThe [Node.js TLS](http://nodejs.org/api/tls.html) page has additional information about the following options.\n\n### no_starttls_ports\n\nAn array of incoming ports on which Haraka will not advertise STARTTLS capability.\n\n```ini\nno_starttls_ports[]=2525\n```\n\n### force_tls_hosts\n\nFor known good TLS hosts, it's possible to force that the outbound mailer will only connect via secure sockets. This makes Haraka use _forced TLS_ instead of _opportunistic TLS_. For forced TLS, the STARTTLS upgrade must succeed with a valid certificate (overriding `rejectUnauthorized`). The list is matched both against the host (MX record or `nexthop` in `relay_dest_domains.ini`), and the domain name of the email address.\n\nNote: unlike `no_tls_hosts`, this feature is implemented as an array:\n\n```ini\n[outbound]\nforce_tls_hosts[]=172.17.123.1\nforce_tls_hosts[]=172.17.124.0/24\nforce_tls_hosts[]=mx.example.org\nforce_tls_hosts[]=example.com\n```\n\n### ciphers\n\nA list of allowable ciphers to use. Example:\n\n    ciphers=EECDH+AESGCM:EDH+aRSA+AESGCM:EECDH+AES256:EDH+aRSA+AES256:EECDH+AES128:EDH+aRSA+AES128:RSA+AES:RSA+3DES\n\nSee also: [Mozilla SSL configuration generator](https://ssl-config.mozilla.org/) and the [SSLlabs Test Page](https://www.ssllabs.com/ssltest/index.html)\n\n### minVersion\n\nSpecifies minimum allowable TLS protocol version to use. Example:\n\n     minVersion=TLSv1.1\n\nIf unset, the default is Node's `tls.DEFAULT_MIN_VERSION` constant\n(currently `'TLSv1.2'`). Valid values: `'TLSv1.3'`, `'TLSv1.2'`,\n`'TLSv1.1'`, `'TLSv1'`.\n\n### honorCipherOrder\n\nIf specified, the list of configured ciphers is treated as the cipher priority from highest to lowest. The first matching cipher will be used, instead of letting the client choose. The default is `true`.\n\n### ecdhCurve\n\nSpecifies the elliptic curve used for ECDH or ECDHE ciphers.\nOnly one curve can be specified. The default is `prime256v1` (NIST P-256).\n\n### dhparam\n\nSpecifies the file containing the diffie-hellman parameters to use for DH or DHE key exchange. If this param or file is missing, it will be generated automatically. Default: `dhparams.pem`.\n\n### requestCert\n\nWhether Haraka should request a certificate from a connecting client.\n\n    requestCert=[true|false]  (default: true)\n\n### rejectUnauthorized\n\nReject connections from clients without a CA validated TLS certificate.\n\n    rejectUnauthorized=[true|false]  (default: false)\n\n### requireAuthorized\n\nWhen `rejectUnauthorized=false`, require validated TLS certificates on just the specified ports.\n\n```ini\nrequireAuthorized[]=465\n;requireAuthorized[]=587\n```\n\n### secureProtocol\n\nLegacy. Specifies the OpenSSL API function used to negotiate TLS — see\nthe [OpenSSL API page](https://www.openssl.org/docs/manmaster/ssl/ssl.html).\nPrefer `minVersion` for modern setups; `secureProtocol` is only useful\nto lock to a specific historic protocol.\n\n### requestOCSP\n\nSpecifies that OCSP Stapling should be enabled, according to RFC 6066.\nStapling of OCSP messages allows the client to receive these along the\nTLS session setup instead of delaying the session setup by requiring a\nseparate http connection to the OCSP server.\n\n    requestOCSP=[true|false]  (default: false)\n\nOCSP responses from the OCSP server are cached in memory for as long as\nthey are valid, and get refreshed after that time. A server restart\nrequires the OCSP responses to be fetched again upon the first client\nconnection.\n\n## Inbound Specific Configuration\n\nBy default the above options are shared with outbound mail (either\nusing `smtp_forward`, `smtp_proxy` or plain outbound mail heading to\nan external destination). To make these options specific to inbound\nmail, put them under an `[inbound]` parameter group. Outbound options\ncan go under an `[outbound]` parameter group, and plugins that use\nSMTP tls for queueing such as `smtp_proxy` and `smtp_forward` can\nuse that plugin name for plugin specific options.\n\n## `[redis]` section\n\nThis section is mainly used to enable so called _TLS NO-GO_ feature that essentially stops advertising/using TLS if there was a problem setting it up previously. We use `no_tls|ip.add.re.ss` key to store the flag in redis. There are a couple of settings that control the behavior:\n\n`disable_for_failed_hosts = true` to enable the feature\n\n`disable_expiry = 604800` to set for how long we disable TLS for failing host, in seconds\n\n`disable_inbound_expiry = 3600` same as above, but applies to inbound (aka STARTTLS capability) only\n","docs/plugins/toobusy.md":"# toobusy\n\nThis plugin will stop Haraka accepting new connections when the event loop\nlatency is too high.\n\nSee https://github.com/STRML/node-toobusy for details.\n\nTo use this plugin you must install the [`toobusy-js`](https://www.npmjs.com/package/toobusy-js)\nmodule — it is not bundled with Haraka. From your Haraka install\ndirectory:\n\n```sh\nnpm install toobusy-js\n```\n\nThis plugin registers on the `connect` hook with priority `-100`, so it\nruns ahead of other `connect`/`lookup_rdns` plugins. Listing it near the\ntop of `config/plugins` is still a good idea for clarity.\n\n## Configuration\n\nIf you wish to override the default maxLag value of 70ms then add the desired\nvalue to config/toobusy.maxlag. This can be set and changed at runtime and\nno restart is required.\n\nNote that if you set the maxLag value to <10 then this will cause the toobusy\nmodule to raise an exception which will cause Haraka to stop.\n","docs/plugins/xclient.md":"# xclient\n\nImplements the [XCLIENT](http://www.postfix.org/XCLIENT_README.html) protocol.\n\n## configuration\n\n- xclient.hosts\n\n  A list of IP addresses, one per line that should be allowed to use the\n  XCLIENT protocol. Localhost (127.0.0.1 or ::1) is allowed implicitly.\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"<!-- agents-version: 3 -->\n\n# AGENTS.md\n\nShared instructions for every coding agent in this repo. The tool-specific files\n(`CLAUDE.md`, `GEMINI.md`, `.github/copilot-instructions.md`) defer to this one.\nIf a package-level instruction file exists, it is authoritative for that package.\n\n## Repository model\n\n- This file is shared across every Haraka repository. It describes the whole family — not just this repo. Each repo is an independent npm package, there is no root workspace. Run every command from the package root.\n- Repos are worked on both standalone and as sibling checkouts in a combined tree (`Haraka/`, `plugin/<name>/`, …). Don't assume sibling packages are present on disk.\n- The family:\n  - `Haraka` — core SMTP server (has `./run_tests`).\n  - `haraka-plugin-<name>` — optional plugins; may depend on each other (e.g. bounce → spf).\n  - `haraka-config` — config loader with hot-reload.\n  - `haraka-results`, `haraka-notes` — per-connection result / note tracking.\n  - `haraka-net-utils`, `haraka-utils`, `haraka-constants`, `haraka-dsn`, `haraka-tld`, `haraka-message-stream` — shared libraries.\n  - `@haraka/email-address` — the RFC 5321/5322 parser (supersedes the deprecated `address-rfc282x`).\n  - `@haraka/eslint-config`, `haraka-test-fixtures`.\n\n## Working agreement\n\n- Do only what was asked. When you spot an adjacent bug or smell, surface it and ask before expanding scope. Don't silently refactor, and don't ignore it.\n- Preserve compatibility; break it only for an explicit, stated reason.\n- For protocol behavior, identify the relevant RFC and verify conformance against the existing implementation.\n\n## Source control\n\n- Never run history- or remote-mutating commands (`git commit`, `git push`, `git tag`, `gh pr create`) unless explicitly asked.\n- create diffs; the human reviews, commits, and pushes.\n- No trailers on commit messages or PR bodies.\n- When asked to generate a PR:\n  - keep it succinct, terse, and DRY\n  - Post a link to it in the console\n- Document changes relative to main in CHANGELOG.md\n  - one clear concise entry per change, < 50 chars\n  - prefer Conventional Commit format, imperative mood.\n  - Rationale belongs in the code or PR, not the bullet.\n\n## Coding standards\n\n- Target current Node LTS; prefer ES2024 over legacy patterns.\n- Existing code is CommonJS (`require`/`exports`) — match it. New modules should use ESM with CJS interop (see `@haraka/email-address`).\n- Add `node:` prefixes to built-in requires in any file you touch (`require('fs')` → `require('node:fs')`).\n- Prefer: promise APIs (`fs/promises`), `for...of`/`for...in` over `forEach`, `node:readline` for line parsing, template literals over concatenation, `true`/`false` over `1`/`0`, and guard-style early returns.\n- Remove commented-out code (it lives in git history). `npm run qlty` must pass without warnings.\n\n## Concision\n\n- Don't Repeat Yourself: Say each thing exactly once, in the place it belongs.\n- Sentences: must carry non-obvious facts the reader cannot get from the surroundings.\n- Cut scaffolding, keep facts.\n\nNever write these:\n\n- **Announcements.** \"One question:\", \"Two reasons:\", \"The point is:\", \"Here's the thing:\". Say the thing. If you announce a count, the count must be right — so don't announce one.\n- **Justification tails.** A clause after a comma or dash that argues for the clause before it: \"…and a guess there is worse than none\", \"…which is the failure this exists to prevent\".\n- **The contrast tic.** \"X, not Y\" and \"X rather than Y\" where Y is obvious. State X.\n- **Clauses doing an adjective's job.** \"a setting nobody has written\" is \"an unwritten setting\"; \"a copy nobody has opened\" is \"an unopened copy\"; \"still has to\" is \"must\". A state with a name takes the name.\n- **Closing morals.** A final sentence restating the paragraph's point.\n\n## Comments\n\n- Keep only WHY comments — a hidden constraint, an invariant, a workaround for a specific bug, or an RFC citation that explains otherwise-surprising behavior.\n- Delete WHAT comments that restate the code.\n- When a rename makes a comment redundant, delete it.\n- Never narrate history or audit findings.\n- Never count the code — counts rot.\n- Prefer self-documenting code: a better name beats any comment.\n\n## Haraka plugins\n\n- Full hook/API reference: `docs/Plugins.md` in the `Haraka` core repo.\n- A plugin is an npm package: `index.js` (`exports.register` + hook handlers), `config/` (default `.ini`/`.json`/`.yaml`), `test/`, `README.md`.\n- Register hooks in `exports.register` with `this.register_hook('phase', 'method'[, priority])`.\n- Hook handlers take `(next, connection)` (rcpt hooks also take `rcpt`) and must call `next` exactly once. Gate early — return `next()` on missing transaction, disabled config, or skip conditions. Signal a verdict with `next(DENY|DENYSOFT|OK, msg)`; `DENY`/`OK`/etc. are plugin-scope globals (no import).\n- Results: `connection.transaction.results.add(this, { pass|fail|skip|msg|err, emit })`; query with `results.has(plugin, list, search)`. `emit: true` already logs the collated line — don't also `loginfo`/`logerror` the same thing. results.add(this, {err}) always logs.\n- Config loads via `config.get` with a hot-reload callback; declare every boolean or it stays a string and `=== true/false` silently fails:\n  ```js\n  this.cfg = this.config.get('name.ini', { booleans: ['+a.b', '-c.d'] }, () => this.load())\n  ```\n- Keep handlers thin. Push pure decision logic and I/O into `lib/*.js` as pure functions that return a verdict/value; the handler just maps that to `results.add` + `next`. For external I/O (DNS, network), expose an injectable seam — a swappable function whose default is the real implementation — so tests run without mocks.\n- If you add files outside `index.js` (e.g. a `lib/` dir), add them to `package.json` `files` so they publish.\n\n## Testing\n\n- Test real behavior and observable outcomes — `results`, return codes, emitted headers, side effects — not how a function was called. Asserting call shape (`calledWith`, arity, call counts) tests the test and hides signature drift.\n- Mocks/stubs are a smell. Prefer real inputs; when you must isolate a dependency, inject a seam and assert the outcome. Never leave a stub that neuters the path under test — that yields green tests proving nothing.\n- For bug fixes, add a failing test first, then fix.\n- Every feature ships with meaningful tests. A `.skip` is a coverage hole: fix it or delete it.\n- Use `node:test` and `node:assert/strict` for new tests and Mocha migrations. Plugin tests use `haraka-test-fixtures` (`makePlugin`, `makeConnection`, `callHook`).\n- Run the package's `lint`, `prettier`, and `format` before handoff.\n\n## Commands (run inside the target package)\n\n- Test: `npm test`. Single file: `node --test test/path/to/file.js`.\n- Haraka core repo only: `./run_tests [test/plugins/foo.js]`.\n- Coverage: `npm run test:coverage`; lcov: `npm run test:coverage:lcov`. Keep coverage at/above ~90%.\n- If coverage output includes non-source files (e.g. `package.json`, `test/*`), scope it with `--test-coverage-include` (preferred when the list is short) or `--test-coverage-exclude`.\n- Lint/format: `npm run lint` / `prettier` / `format`. Version drift: `npm run versions[:fix]`.\n\n## Package script parity\n\n- node:test packages should expose `test`, `test:coverage`, `test:coverage:lcov`, `lint`, `prettier`, `format` with matching shapes across siblings. Standardize on node:test coverage (not c8); add the canonical scripts when touching a package that lacks them:\n  ```jsonc\n  \"test:coverage\": \"node --test --experimental-test-coverage\",\n  \"test:coverage:lcov\": \"mkdir -p coverage && node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info\"\n  ```\n\n## Repo badges\n\n- Code climate is deprecated. Update with qlty.sh instead.\n- The canonical format for badges should be:\n  - Top of README.md:\n    - [![Test][ci-img]][ci-url] [![Cover][cov-img]][cov-url] [![Qlty][qlty-img]][qlty-url]\n  - Bottom of README.md:\n    - [ci-img]: https://github.com/haraka/<name>/actions/workflows/ci.yml/badge.svg\n    - [ci-url]: https://github.com/haraka/<name>/actions/workflows/ci.yml\n    - [cov-img]: https://codecov.io/github/haraka/<name>/coverage.svg\n    - [cov-url]: https://codecov.io/github/haraka/<name>\n    - [qlty-img]: https://qlty.sh/gh/haraka/projects/<name>/maintainability.svg\n    - [qlty-url]: https://qlty.sh/gh/haraka/projects/<name>\n","isInternal":false,"tokens":2183,"sizeBytes":8472},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"see AGENTS.md\n","isInternal":false,"tokens":5,"sizeBytes":14},{"name":"copilot-instructions.md","path":".github/copilot-instructions.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/.github/copilot-instructions.md","title":"GitHub Copilot Instructions","category":"copilot-instructions","format":"markdown","content":"<!-- agents-version: 3 -->\n\n# AGENTS.md\n\nShared instructions for every coding agent in this repo. The tool-specific files\n(`CLAUDE.md`, `GEMINI.md`, `.github/copilot-instructions.md`) defer to this one.\nIf a package-level instruction file exists, it is authoritative for that package.\n\n## Repository model\n\n- This file is shared across every Haraka repository. It describes the whole family — not just this repo. Each repo is an independent npm package, there is no root workspace. Run every command from the package root.\n- Repos are worked on both standalone and as sibling checkouts in a combined tree (`Haraka/`, `plugin/<name>/`, …). Don't assume sibling packages are present on disk.\n- The family:\n  - `Haraka` — core SMTP server (has `./run_tests`).\n  - `haraka-plugin-<name>` — optional plugins; may depend on each other (e.g. bounce → spf).\n  - `haraka-config` — config loader with hot-reload.\n  - `haraka-results`, `haraka-notes` — per-connection result / note tracking.\n  - `haraka-net-utils`, `haraka-utils`, `haraka-constants`, `haraka-dsn`, `haraka-tld`, `haraka-message-stream` — shared libraries.\n  - `@haraka/email-address` — the RFC 5321/5322 parser (supersedes the deprecated `address-rfc282x`).\n  - `@haraka/eslint-config`, `haraka-test-fixtures`.\n\n## Working agreement\n\n- Do only what was asked. When you spot an adjacent bug or smell, surface it and ask before expanding scope. Don't silently refactor, and don't ignore it.\n- Preserve compatibility; break it only for an explicit, stated reason.\n- For protocol behavior, identify the relevant RFC and verify conformance against the existing implementation.\n\n## Source control\n\n- Never run history- or remote-mutating commands (`git commit`, `git push`, `git tag`, `gh pr create`) unless explicitly asked.\n- create diffs; the human reviews, commits, and pushes.\n- No trailers on commit messages or PR bodies.\n- When asked to generate a PR:\n  - keep it succinct, terse, and DRY\n  - Post a link to it in the console\n- Document changes relative to main in CHANGELOG.md\n  - one clear concise entry per change, < 50 chars\n  - prefer Conventional Commit format, imperative mood.\n  - Rationale belongs in the code or PR, not the bullet.\n\n## Coding standards\n\n- Target current Node LTS; prefer ES2024 over legacy patterns.\n- Existing code is CommonJS (`require`/`exports`) — match it. New modules should use ESM with CJS interop (see `@haraka/email-address`).\n- Add `node:` prefixes to built-in requires in any file you touch (`require('fs')` → `require('node:fs')`).\n- Prefer: promise APIs (`fs/promises`), `for...of`/`for...in` over `forEach`, `node:readline` for line parsing, template literals over concatenation, `true`/`false` over `1`/`0`, and guard-style early returns.\n- Remove commented-out code (it lives in git history). `npm run qlty` must pass without warnings.\n\n## Concision\n\n- Don't Repeat Yourself: Say each thing exactly once, in the place it belongs.\n- Sentences: must carry non-obvious facts the reader cannot get from the surroundings.\n- Cut scaffolding, keep facts.\n\nNever write these:\n\n- **Announcements.** \"One question:\", \"Two reasons:\", \"The point is:\", \"Here's the thing:\". Say the thing. If you announce a count, the count must be right — so don't announce one.\n- **Justification tails.** A clause after a comma or dash that argues for the clause before it: \"…and a guess there is worse than none\", \"…which is the failure this exists to prevent\".\n- **The contrast tic.** \"X, not Y\" and \"X rather than Y\" where Y is obvious. State X.\n- **Clauses doing an adjective's job.** \"a setting nobody has written\" is \"an unwritten setting\"; \"a copy nobody has opened\" is \"an unopened copy\"; \"still has to\" is \"must\". A state with a name takes the name.\n- **Closing morals.** A final sentence restating the paragraph's point.\n\n## Comments\n\n- Keep only WHY comments — a hidden constraint, an invariant, a workaround for a specific bug, or an RFC citation that explains otherwise-surprising behavior.\n- Delete WHAT comments that restate the code.\n- When a rename makes a comment redundant, delete it.\n- Never narrate history or audit findings.\n- Never count the code — counts rot.\n- Prefer self-documenting code: a better name beats any comment.\n\n## Haraka plugins\n\n- Full hook/API reference: `docs/Plugins.md` in the `Haraka` core repo.\n- A plugin is an npm package: `index.js` (`exports.register` + hook handlers), `config/` (default `.ini`/`.json`/`.yaml`), `test/`, `README.md`.\n- Register hooks in `exports.register` with `this.register_hook('phase', 'method'[, priority])`.\n- Hook handlers take `(next, connection)` (rcpt hooks also take `rcpt`) and must call `next` exactly once. Gate early — return `next()` on missing transaction, disabled config, or skip conditions. Signal a verdict with `next(DENY|DENYSOFT|OK, msg)`; `DENY`/`OK`/etc. are plugin-scope globals (no import).\n- Results: `connection.transaction.results.add(this, { pass|fail|skip|msg|err, emit })`; query with `results.has(plugin, list, search)`. `emit: true` already logs the collated line — don't also `loginfo`/`logerror` the same thing. results.add(this, {err}) always logs.\n- Config loads via `config.get` with a hot-reload callback; declare every boolean or it stays a string and `=== true/false` silently fails:\n  ```js\n  this.cfg = this.config.get('name.ini', { booleans: ['+a.b', '-c.d'] }, () => this.load())\n  ```\n- Keep handlers thin. Push pure decision logic and I/O into `lib/*.js` as pure functions that return a verdict/value; the handler just maps that to `results.add` + `next`. For external I/O (DNS, network), expose an injectable seam — a swappable function whose default is the real implementation — so tests run without mocks.\n- If you add files outside `index.js` (e.g. a `lib/` dir), add them to `package.json` `files` so they publish.\n\n## Testing\n\n- Test real behavior and observable outcomes — `results`, return codes, emitted headers, side effects — not how a function was called. Asserting call shape (`calledWith`, arity, call counts) tests the test and hides signature drift.\n- Mocks/stubs are a smell. Prefer real inputs; when you must isolate a dependency, inject a seam and assert the outcome. Never leave a stub that neuters the path under test — that yields green tests proving nothing.\n- For bug fixes, add a failing test first, then fix.\n- Every feature ships with meaningful tests. A `.skip` is a coverage hole: fix it or delete it.\n- Use `node:test` and `node:assert/strict` for new tests and Mocha migrations. Plugin tests use `haraka-test-fixtures` (`makePlugin`, `makeConnection`, `callHook`).\n- Run the package's `lint`, `prettier`, and `format` before handoff.\n\n## Commands (run inside the target package)\n\n- Test: `npm test`. Single file: `node --test test/path/to/file.js`.\n- Haraka core repo only: `./run_tests [test/plugins/foo.js]`.\n- Coverage: `npm run test:coverage`; lcov: `npm run test:coverage:lcov`. Keep coverage at/above ~90%.\n- If coverage output includes non-source files (e.g. `package.json`, `test/*`), scope it with `--test-coverage-include` (preferred when the list is short) or `--test-coverage-exclude`.\n- Lint/format: `npm run lint` / `prettier` / `format`. Version drift: `npm run versions[:fix]`.\n\n## Package script parity\n\n- node:test packages should expose `test`, `test:coverage`, `test:coverage:lcov`, `lint`, `prettier`, `format` with matching shapes across siblings. Standardize on node:test coverage (not c8); add the canonical scripts when touching a package that lacks them:\n  ```jsonc\n  \"test:coverage\": \"node --test --experimental-test-coverage\",\n  \"test:coverage:lcov\": \"mkdir -p coverage && node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info\"\n  ```\n\n## Repo badges\n\n- Code climate is deprecated. Update with qlty.sh instead.\n- The canonical format for badges should be:\n  - Top of README.md:\n    - [![Test][ci-img]][ci-url] [![Cover][cov-img]][cov-url] [![Qlty][qlty-img]][qlty-url]\n  - Bottom of README.md:\n    - [ci-img]: https://github.com/haraka/<name>/actions/workflows/ci.yml/badge.svg\n    - [ci-url]: https://github.com/haraka/<name>/actions/workflows/ci.yml\n    - [cov-img]: https://codecov.io/github/haraka/<name>/coverage.svg\n    - [cov-url]: https://codecov.io/github/haraka/<name>\n    - [qlty-img]: https://qlty.sh/gh/haraka/projects/<name>/maintainability.svg\n    - [qlty-url]: https://qlty.sh/gh/haraka/projects/<name>\n","isInternal":false,"tokens":2183,"sizeBytes":8472},{"name":"aliases.md","path":"docs/plugins/aliases.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/aliases.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# aliases\n\nRepackaged as [haraka-plugin-aliases](https://github.com/haraka/haraka-plugin-aliases).\n","isInternal":false,"tokens":28,"sizeBytes":99},{"name":"auth_bridge.md","path":"docs/plugins/auth/auth_bridge.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/auth/auth_bridge.md","title":"Auth Skill","category":"anthropic-skill","format":"markdown","content":"# auth/auth_bridge\n\nThis plugin allows you to authenticate users to remote SMTP servers\nbridging the original user and password to the remote server,\nand proxy the result back to authenticate the client.\n\nThis plugin is meant to be used with the plugin `queue/smtp_bridge`.\n\nIt is different than `auth/auth_proxy` because it doesn't require\nthe AUTH user in user@domain.com format, and it doesn't check that\nthe domain is the configuration file. This plugins simply takes\nthe original user and password and tries to authenticate it in the\nremote SMTP server.\n\n## Configuration\n\nConfiguration is stored in `config/smtp_bridge.ini` and uses the INI\nstyle formatting.\n\nThe configuration of this plugin is simple:\n\n    host=localhost\n    #port=\n    #auth_type=\n    #priority=10\n\n- host: the host where you will be authenticating and posting,\n  for example `smtp.host.tld`, `192.0.2.25:587`, or `[2001:db8::1]:587`.\n  This is the only setting required. See [Endpoint][endpoint] for the\n  accepted forms. An IPv6 address must be bracketed to include a port.\n\nIf needed you can also set\n\n- port: used when `host` doesn't include one. Defaults to 25.\n\n[endpoint]: https://github.com/haraka/haraka-net-utils#endpoint\n\nThe options `auth_type` and `priority` will be used by `queue/smtp_bridge`\n","isInternal":false,"tokens":309,"sizeBytes":1284},{"name":"auth_ldap.md","path":"docs/plugins/auth/auth_ldap.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/auth/auth_ldap.md","title":"Auth Skill","category":"anthropic-skill","format":"markdown","content":"# auth/auth_ldap\n\nRepackaged as [haraka-plugin-auth-ldap](https://github.com/haraka/haraka-plugin-auth-ldap).\nLoading `auth/auth_ldap` in `config/plugins` is auto-redirected to `auth-ldap`.\n","isInternal":false,"tokens":56,"sizeBytes":190},{"name":"auth_proxy.md","path":"docs/plugins/auth/auth_proxy.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/auth/auth_proxy.md","title":"Auth Skill","category":"anthropic-skill","format":"markdown","content":"# auth/auth_proxy\n\nThis plugin allows you to authenticate users by domain to remote SMTP servers\nand proxy the result back to authenticate the client.\n\nFor this to work - the AUTH username _must_ be in user@domain.com format\nregardless as to whether the remote SMTP server requires it in this format.\nThe domain part of the username is used to look-up which SMTP servers should\nbe used to authenticate users for that domain.\nWhen sending the AUTH credentials to the remote server, this plugin will try\nand send the full username e.g. user@domain.com first and if this fails it\nwill then strip the @domain.com part and just send the unqualified username.\n\nDue to the way this plugin works - it can only support PLAIN and LOGIN\nauthentication methods and for this reason it requires that STARTTLS be\nused via the tls plugin before it will advertise AUTH capabilities by the\nEHLO command. When connecting to the remote SMTP systems it will always\nattempt STARTTLS if it is offered, but it does _not_ require it, so caution\nshould be exercised.\n\n## Configuration\n\nConfiguration is stored in `config/auth_proxy.ini` and uses the INI\nstyle formatting.\n\nThe configuration of this plugin is simple:\n\n    [domains]\n    domain.com = server1.domain.com:587 server2.domain.com\n\nWhere domain.com is the domain-part of the username equals a list of hosts\nthat should be consulted in host:port format. The :port is optional and will\ndefault to 25. The list of hosts can be space, semi-colon or comma separated.\n\nIf more than host is specified, then subsequent hosts will only be tested if\nthere is some sort of error e.g. timeout, connection or protocol error.\n","isInternal":false,"tokens":356,"sizeBytes":1646},{"name":"auth_vpopmaild.md","path":"docs/plugins/auth/auth_vpopmaild.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/auth/auth_vpopmaild.md","title":"Auth Skill","category":"anthropic-skill","format":"markdown","content":"# auth/auth_vpopmaild\n\nThe `auth/vpopmaild` plugin allows SMTP users to authenticate against a vpopmaild daemon.\n\n## Configuration\n\nThe configuration file is stored in `config/auth_vpopmaild.ini`.\n\n### settings\n\n- host: The host/IP that vpopmaild is listening on (default: localhost).\n\n- port: The TCP port that vpopmaild is listening on (default: 89).\n\n- sysadmin: A colon separated username:password of a vpopmail user with SYSADMIN privileges (see vpopmail/bin/vmoduser -S). This is **only** necessary to support CRAM-MD5 which requires access to the clear text password. On new installs, it's best not to use CRAM-MD5, as it requires storing clear text passwords. Legacy clients with MUAs configured to authenticate with CRAM-MD5 will need this enabled.\n\n- constrain_sender: (default: true). For outbound messages (due to successful AUTH), constrain the envelope sender (MAIL FROM) to the same domain as the authenticated user. This setting, combined with `rate_rcpt_sender` in the [limit](https://github.com/haraka/haraka-plugin-limit) plugin can dramatically reduce the amount of backscatter and spam sent when an email account is compromised.\n\n### Per-domain Configuration\n\nAdditionally, domains can each have their own configuration for connecting\nto vpopmaild. The defaults are the same, so only the differences needs to\nbe declared. Example:\n\n```ini\n[example.com]\nhost=192.168.0.1\nport=999\n\n[example2.com]\nhost=192.168.0.2\nsysadmin=postmaster@example2.com:sekret\n```\n","isInternal":false,"tokens":360,"sizeBytes":1477},{"name":"flat_file.md","path":"docs/plugins/auth/flat_file.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/auth/flat_file.md","title":"Auth Skill","category":"anthropic-skill","format":"markdown","content":"# auth/flat_file\n\nThe `auth/flat_file` plugin allows you to create a file containing username and password combinations, and have relaying users authenticate from that file.\n\nNote that passwords are stored in clear-text, so this may not be a great idea for large scale systems. However the plugin would be a good start for someone looking to implement authentication using some other form of auth.\n\n**Security** - it is recommended to switch to [auth-encfile][url-authencflat] to protect your user credentials.\n\n**IMPORANT NOTE** - this plugin requires that STARTTLS be used via the tls plugin before it will advertise AUTH capabilities by the EHLO command. Localhost and IPs in RFC1918 ranges\nare exempt from this rule.\n\n## Configuration\n\nConfiguration is stored in `config/auth_flat_file.ini`.\n\n- [core]methods\n\nAuthentication methods are listed in the `[core]methods` parameter. Authentification methods are comma separated. Currently supported methods are: `CRAM-MD5`, `PLAIN` and `LOGIN`. The `PLAIN` and `LOGIN` methods are insecure and require TLS to be enabled.\n\n- [core]constrain_sender: (default: true). For outbound messages (due to successful AUTH), constrain the envelope sender (MAIL FROM) to the same domain as the authenticated user. This setting, combined with `rate_rcpt_sender` in the [limit](https://github.com/haraka/haraka-plugin-limit) plugin can dramatically reduce the amount of backscatter and spam sent when an email account is compromised.\n\nExample:\n\n```ini\n[core]\nmethods=PLAIN,LOGIN,CRAM-MD5\nconstrain_sender=true\n```\n\nUsers are stored in the `[users]` section.\n\nExample:\n\n```ini\n[users]\nuser1=password1\nuser@domain.com=password2\n```\n\n[url-authencflat]: https://github.com/AuspeXeu/haraka-plugin-auth-enc-file\n","isInternal":false,"tokens":401,"sizeBytes":1740},{"name":"block_me.md","path":"docs/plugins/block_me.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/block_me.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# block_me\n\nThis plugin allows you to configure an address which mail sent to will be\nparsed for a From: address in the body of the message, and will add that\nfrom address to the `mail_from.blocklist` config file.\n\nEffectively this allows your users to forward spams that got through to a\nparticular mailbox to block them in the future.\n\nNote that this is a system-wide block, and not per-user. Be careful with this.\n\n## Configuration\n\n- `config/block_me.recipient` - a file containing the address to email to\n  get something blocked. For example: **spam@domain.com**.\n\n- `config/block_me.senders` - a file containing a list of email addresses\n  that are allowed to email the dropbox.\n","isInternal":false,"tokens":159,"sizeBytes":685},{"name":"data.signatures.md","path":"docs/plugins/data.signatures.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/data.signatures.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# data.signatures\n\nThis plugin allows you to add string signatures to a configuration file and\nhave this plugin scan the body text of an email for those strings. Mails\nmatching these signatures will be blocked.\n\n## Configuration\n\n- data.signatures\n\n  This file contains a list of strings (one per line) that will be matched.\n","isInternal":false,"tokens":69,"sizeBytes":325},{"name":"delay_deny.md","path":"docs/plugins/delay_deny.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/delay_deny.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# delay_deny\n\nDelays all pre-DATA 'deny' results until the recipients are sent\nand all post-DATA commands until all hook_data_post plugins have run.\nThis allows relays and authenticated users to bypass pre-DATA rejections.\n\n## Configuration\n\nConfiguration options are in config/delay_deny.ini.\n\nThis plugin operates in one of two modes: included and excluded.\n\n### included plugins\n\nA comma or semicolon separated list of denials that are to be included.\nIn this mode, _only_ plugins in the list are bypassed. All other plugins\ncan immediately reject connections.\n\n### excluded plugins\n\nA comma or semicolon separated list of denials that are to be excluded.\nExcluded plugins that are not bypassed and can still immediately reject\nconnections.\n","isInternal":false,"tokens":158,"sizeBytes":744},{"name":"max_unrecognized_commands.md","path":"docs/plugins/max_unrecognized_commands.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/max_unrecognized_commands.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# max_unrecognized_commands\n\nThe functionality of this plugin was folded into\n[haraka-plugin-limit](https://github.com/haraka/haraka-plugin-limit).\nLoading `max_unrecognized_commands` in `config/plugins` is auto-redirected\nto `limit`.\n","isInternal":false,"tokens":56,"sizeBytes":235},{"name":"prevent_credential_leaks.md","path":"docs/plugins/prevent_credential_leaks.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/prevent_credential_leaks.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# prevent_credential_leaks\n\nThis plugin prevents an authenticated user (via SMTP AUTH) from sending\ntheir username and password out in a message (e.g. like replying to a\nphish).\n\nIf their username and password are detected inside the message body, then\nthe message is rejected with the message:\n\n```\nCredential leak detected: never give out your username/password to anyone!\n```\n\nNote that if the username is qualified e.g. user@domain.com - then the\nplugin will search for both `user` and `user@domain.com` for maximum\neffectiveness.\n\n## Configuration\n\nNo configuration is required. Simply add the plugin to your `config/plugins`\nfile. It should be added before any other plugins that run on hook_data_post\nfor maximum efficiency.\n","isInternal":false,"tokens":159,"sizeBytes":732},{"name":"process_title.md","path":"docs/plugins/process_title.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/process_title.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# process_title\n\nThis plugin causes the process title seen by the UNIX 'ps' command to\nbe modified from this:\n\n```\nnode haraka.js -c /etc/haraka\n```\n\nto this:\n\n```\nHaraka (master) cn=11148 cc=1082 cps=21/25.24/79 rcpts=144950/1.84 rps=518/328.18/586 msgs=78815/7.07 mps=302/178.44/329 out=0/0/0 respawn=0\n \\_ Haraka (worker) cn=1646 cc=140 cps=5/3.73/17 rcpts=20310/1.86 rps=75/46.04/102 msgs=10938/6.65 mps=42/24.8/56 out=0/0/0\n \\_ Haraka (worker) cn=1563 cc=168 cps=3/3.54/18 rcpts=19844/1.87 rps=78/45/96 msgs=10627/6.8 mps=49/24.1/53 out=0/0/0\n \\_ Haraka (worker) cn=1852 cc=172 cps=3/4.2/16 rcpts=26278/2.03 rps=93/59.56/114 msgs=12938/6.99 mps=40/29.33/65 out=0/0/0\n \\_ Haraka (worker) cn=1704 cc=187 cps=5/3.86/14 rcpts=23688/1.84 rps=93/53.7/125 msgs=12886/7.56 mps=64/29.21/66 out=0/0/0\n \\_ Haraka (worker) cn=2296 cc=218 cps=2/5.2/20 rcpts=29300/1.78 rps=117/66.4/125 msgs=16489/7.18 mps=40/37.37/66 out=0/0/0\n \\_ Haraka (worker) cn=2091 cc=195 cps=4/4.74/16 rcpts=25646/1.71 rps=84/58.12/117 msgs=14982/7.16 mps=52/33.95/66 out=0/0/0\n```\n\nwhere:\n\n- cn = Total number of connections\n- cc = Total number of concurrent connections\n- cps = Number of connections in the last second / average / maximum\n- rcpts = Total number of recipients / Average number of recipients per message\n- rps = Number of recipients in the last second / average / maximum\n- msgs = Total number of messages / Average number messages per connection\n- mps = Number of messages in the last second / average / maximum\n- out = Mails being processed / Mails waiting to be processed / Mails in temp fail state\n- respawn = Number of worker processes respawned (only under cluster)\n\nIf 'cluster' is used then the master process will show the total\nacross all workers, with the exception of outbound stats.\n\nAll of the counts shown are since the process started, so if a\nworker has been re-started then the counts may not add up.\n\nNote: this plugin should be added at the top of `config/plugins` so\nthat its `connect_init`, `rcpt`, `data`, and `disconnect` hooks run\nbefore any plugin that might short-circuit those hooks.\n","isInternal":false,"tokens":760,"sizeBytes":2096},{"name":"deliver.md","path":"docs/plugins/queue/deliver.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/queue/deliver.md","title":"Queue Skill","category":"anthropic-skill","format":"markdown","content":"# queue/deliver\n\nThis plugin is now redundant. Outbound delivery is now built into Haraka.\n","isInternal":false,"tokens":22,"sizeBytes":91},{"name":"discard.md","path":"docs/plugins/queue/discard.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/queue/discard.md","title":"Queue Skill","category":"anthropic-skill","format":"markdown","content":"# discard\n\nThis plugin will discard a message by pretending that the message was queued.\n\nIt is designed to be used by other plugins which request the message be\ndiscard by setting a connection or transaction note that this plugin\nchecks.\n\nIt uses the 'queue' hook, so it runs after all the plugins that hook on `data_post`.\n\nIf you use the 'quarantine' plug-in then this plugin should run _after_ it.\n\nUSE THIS PLUGIN WITH CARE!\n\n# Enable\n\nEnable by adding a `queue/discard` entry in `config/plugins` **before** your\nother queue plugins that perform actual deliveries.\n\n# Usage\n\nSet\n\n```javascript\nconnection.notes.discard = [1 | true]\n```\n\nor\n\n```javascript\nconnection.transaction.notes.discard = [1 | true]\n```\n","isInternal":false,"tokens":164,"sizeBytes":714},{"name":"lmtp.md","path":"docs/plugins/queue/lmtp.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/queue/lmtp.md","title":"Queue Skill","category":"anthropic-skill","format":"markdown","content":"# queue/lmtp\n\nThis plugin delivers inbound mail via LMTP.\n\n## Configuration\n\nLMTP is enabled by adding `queue/lmtp` to config/plugins. LMTP delivery is configured in `config/lmtp.ini` . By default, all inbound messages are forwarded to the host specified in the `[main]` section. Domain specific routes can be specified by creating additional sections with the same host/port or path options.\n\n### lmtp.ini\n\n```ini\n; defaults\nhost=localhost\nport=24\n\n[example1.com]\n; Goes elsewhere\nhost=10.1.1.1\nport=2400\n\n[example2.com]\n; Using unix domain sockets\npath = /tmp/blah_com_socket\n```\n","isInternal":false,"tokens":151,"sizeBytes":582},{"name":"qmail-queue.md","path":"docs/plugins/queue/qmail-queue.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/queue/qmail-queue.md","title":"Queue Skill","category":"anthropic-skill","format":"markdown","content":"# queue/qmail-queue\n\nThis plugin delivers the mail to the `qmail-queue` program, which can be used\nfor both inbound and outbound delivery.\n\n## Configuration\n\n- qmail-queue.path\n\n  The path to the `qmail-queue` binary. Default: `/var/qmail/bin/qmail-queue`\n\n- qmail-queue.ini\n  - enable_outbound=true\n\n    Deliver outbound email to qmail. Set to false to use Haraka's\n    separate Outbound mail routing (MX based delivery)).\n","isInternal":false,"tokens":111,"sizeBytes":424},{"name":"quarantine.md","path":"docs/plugins/queue/quarantine.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/queue/quarantine.md","title":"Queue Skill","category":"anthropic-skill","format":"markdown","content":"# quarantine\n\nThis plugin will save a message (in message/rfc822 format) to a specified\ndirectory, which will be created automatically if it does not already exist,\na dated sub-folder is also added to the end of the path specified in YYYYMMDD\nformat.\n\nIt is designed to be used by other plugins which request the message be\nquarantined by setting a connection or transaction note that this plugin\nchecks.\n\nNOTE: this plugin simply saves a copy of the message. It does not reject or\ndiscard the message and relies on another plugin to perform this function.\n\nIt uses the 'queue' hook, so that it runs after all the 'data_post' plugins\nand should be listed in 'config/plugins' to run before your queue hooks that\nperform actual deliveries.\n\nTo ensure that only completely written files are present in the quarantine,\nthe files are written to a temporary directory first and then hardlinked to\nthe final destination before the temporary file is deleted.\n\nThe temporary directory is 'quarantine_path/tmp' which defaults to:\n/var/spool/haraka/quarantine/tmp.\n\nUpon start-up, any files present in the temporary directory are deleted\nsyncronously prior to any messages being accepted.\n\n## Configuration\n\nThis plugin looks for 'quarantine.ini' in the config directory.\n\n- quarantine_path (default: /var/spool/haraka/quarantine)\n\n  The default base path to save the quarantine files to. It will be created\n  if it does not already exist.\n\n## Usage\n\nIf you wish to keep a copy of the message in your plugin, simply either:\n\n```javascript\nconnection.notes.quarantine = [1 | true | 'sub/directory/path']\n```\n\nor\n\n```javascript\nconnection.transaction.notes.quarantine = [1 | true | 'sub/directory/path']\n```\n\ne.g.\n\n```javascript\nconnection.notes.quarantine = 1\n```\n\nwould save the message to '/var/spool/quarantine/haraka/YYYYMMDD/UUID' where\nYYYMMDD and UUID are expanded to current date and transaction UUID.\n\nand\n\n```javascript\nconnection.notes.quarantine = 'corpus'\n```\n\nwould save the message to '/var/spool/quarantine/haraka/corpus/YYYYMMDD/UUID'.\n\nNote: you can specify 'corpus/foo' or 'corpus/foo/bar' and the directories will\nbe automatically created. Do not add any leading or trailing slashes.\n\nBy default - after the message is quarantined, the plugin will tell Haraka to\ncontinue to the next plugin. You can specify a different action like DENY or\nOK and supply an optional message using the following notes:\n\n```javascript\nconnection.notes.quarantine_action = [OK, 'Message quarantined']\nconnection.transaction.notes.quarantine_action = [DENY, 'Message rejected']\n```\n\nIf you don't want to supply a specific message back to the client you can\nalso just specify a return code:\n\n```javascript\nconnection.notes.quarantine_action = OK\nconnection.transaction.notes.quarantine_action = DENY\n```\n","isInternal":false,"tokens":638,"sizeBytes":2790},{"name":"smtp_bridge.md","path":"docs/plugins/queue/smtp_bridge.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/queue/smtp_bridge.md","title":"Queue Skill","category":"anthropic-skill","format":"markdown","content":"# queue/smtp_bridge\n\nThis plugin delivers to another SMTP server, bridging the authentication\ndetails and post data from the initial connection.\n\nThis plugin is meant to be used with the plugin `auth/auth_bridge`.\n\nIt is different than `queue/smtp_proxy` or `queue/smpt_forward` because\nit doesn't use the AUTH details from a configuration file. This plugins\nsimply post the data from the original connection to the remote SMTP server\nusing the original AUTH details.\n\n## Configuration\n\nConfiguration is stored in `config/smtp_bridge.ini` and uses the INI\nstyle formatting.\n\nThe configuration of this plugin is simple:\n\n    host=localhost\n    #port=\n    #auth_type=\n    #priority=10\n\n- host: the host where you will be authenticating and posting,\n  for example `smtp.host.tld`. This is the only setting required.\n\nIf needed you can also set\n\n- port: default to empty and Haraka will use 25.\n- auth_type: default to empty and Haraka will try to pick an appropriate method.\n- priority: default to 10.\n","isInternal":false,"tokens":225,"sizeBytes":999},{"name":"smtp_forward.md","path":"docs/plugins/queue/smtp_forward.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/queue/smtp_forward.md","title":"Queue Skill","category":"anthropic-skill","format":"markdown","content":"# queue/smtp_forward\n\n==================\n\nThis plugin delivers to another mail server. This is a common setup when you want to have a mail server with a solid pedigree of outbound delivery to other hosts, and inbound delivery to users.\n\nIn comparison to `queue/smtp_proxy`, this plugin waits until queue time to attempt the ongoing connection. This can be a benefit in reducing connections to your inbound mail server when you have content filtering (such as spamassassin) enabled. A possible downside is that it also delays recipient validation that the ongoing mail server may provide until queue time.\n\n## Configuration\n\n---\n\nConfiguration is stored in smtp_forward.ini in the following keys:\n\n- enable_outbound=[true]\n\n  SMTP forward outbound messages (set to false to enable Haraka's separate Outbound mail routing (MX based delivery)).\n\n- host=HOST\n\n  The host to connect to.\n\n- port=PORT\n\n  The port to connect to. Default: 25\n\n- connect_timeout=SECONDS\n\n  The maximum amount of time to wait when creating a new connection to the host. Default: 30 seconds.\n\n- timeout=SECONDS\n\n  The amount of seconds to let a backend connection live idle in the connection pool. This should always be less than the global plugin timeout, which should in turn be less than the connection timeout.\n\n- max_connections=NUMBER\n\n  Maximum number of connections at any given time. Default: 1000\n\n- enable_tls=[true]\n\n  Enable opportunistic TLS with the forward host via `STARTTLS` (if the host advertises it). This plugin does not work with implicit SMTP over TLS.\n\n- auth_type=[plain\\|login]\n\n  Enable PLAIN or LOGIN SMTP AUTH. This is required to enable AUTH.\n\n- auth_user=USERNAME\n\n  SMTP AUTH username to use.\n\n- auth_pass=PASSWORD\n\n  SMTP AUTH password to use.\n\n- queue\n\n  Which queue plugin to use. Default: undefined. The default bahavior is to use smtp_forward for inbound connections and outbound for relaying connections. This option is used for complex mail routes.\n\n- check_sender=false\n\n  Requires that sender domains defined in smtp_forward.ini (see Per-Domain below) have relaying privileges. This is a form of spoof prevention and assumes that any mail clients have relaying or AUTH privileges. This is usually the case.\n\n- check_recipient=false\n\n  By default, Haraka accepts no emails until a recipient plugin has been configured to accept mails for a domain. The simplest common case is the in_host_list plugin with a list of domains in config/host_list. An alternative is to set `check_recipient=true` and list each domain in a definition block in smtp_forward.ini (see Per-Domain Configuration). An example for two domains:\n\n  [example.com]\n  [example.net]\n\n- [tls]\n\nClient STARTTLS options are assembled by merging:\n\n1. `tls.ini` `[main]` — the global Haraka TLS config\n2. `smtp_forward.ini` `[tls]` — overrides. Anything set here wins.\n\nExample `smtp_forward.ini` `[tls]` section:\n\n    [tls]\n    rejectUnauthorized=true\n    minVersion=TLSv1.2\n    no_tls_hosts[]=10.0.0.5\n\nPer-domain `enable_tls=false` still disables STARTTLS for that backend. Per-domain TLS cipher/cert overrides are not currently supported.\n\nChanges to `tls.ini` require a Haraka restart to apply to the forward path; changes to `smtp_forward.ini` are picked up by the existing reload hook.\n\n# Per-Domain Configuration\n\nMore specific forward routes for domains can be defined. The domain is chosen based on the value of the `domain_selector` config variable.\n\nWhen `domain_selector` is set to `rcpt_to` (the default), more specific routes are only honored for SMTP connections with a single recipient or SMTP connections where every recipient host is identical.\n\nWhen `domain_selector` is set to `mail_from`, it first searches for configuration using the complete email address, falls back to the domain if not found, then to main configuration.\n\nenable_outbound can be set or unset on a per-domain level to enable or disable forwarding for specific domains.\n\n    # default SMTP host\n    host=1.2.3.4\n    # auth_type=plain\n    # auth_user=user\n    # auth_user=pass\n\n    [example1.com]\n    host=1.2.3.5\n    # auth_type=plain\n    # auth_user=user\n    # auth_pass=pass\n\n    [email@example1.com]\n    host=1.2.3.5\n    # auth_type=plain\n    # auth_user=user\n    # auth_pass=pass\n\n    [example2.com]\n    host=1.2.3.5\n\n    [example3.com]\n    host=1.2.3.6\n\n    [example4.com]\n    enable\\_outbound=false\n\n# Split host forward routing\n\nWhen an incoming email transaction has multiple recipients with different forward routes, recipients to subsequent forward routes are deferred. Example: an incoming email transaction has recipients user@example1.com, user@example2.com, and user@example3.com. The first two recipients will be accepted (they share the same forward destination) and the latter will be deferred. It will arrive in a future delivery attempt by the remote.\n","isInternal":false,"tokens":1101,"sizeBytes":4839},{"name":"smtp_proxy.md","path":"docs/plugins/queue/smtp_proxy.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/queue/smtp_proxy.md","title":"Queue Skill","category":"anthropic-skill","format":"markdown","content":"# queue/smtp_proxy\n\n================\n\nThis plugin delivers to another mail server. This is a common setup when you want to have a mail server with a solid pedigree of outbound delivery to other hosts, and inbound delivery to users.\n\nIn comparison to `queue/smtp_forward`, this plugin makes a connection at MAIL FROM time to the ongoing SMTP server. This can be a benefit in that you get any SMTP-time filtering that the ongoing server provides, in particular one important facility to some setups is recipient filtering.\n\nBe aware that other than connect and HELO-time filtering, you will have as many connections to your ongoing SMTP server as you have to Haraka.\n\n## Configuration\n\n---\n\nConfiguration is stored in smtp_proxy.ini in the following keys:\n\n- enable_outbound=[true]\n\n  SMTP proxy outbound messages (set to false to enable Haraka's\n  separate Outbound mail routing (MX based delivery)).\n\n- host=HOST\n\n  The host to connect to.\n\n- port=PORT\n\n  The port to connect to.\n\n- connect_timeout=SECONDS\n\n  The maximum amount of time to wait when creating a new connection\n  to the host. Default if unspecified is 30 seconds.\n\n- timeout=SECONDS\n\n  The amount of seconds to let a backend connection live idle in the\n  proxy pool. This should always be less than the global plugin timeout,\n  which should in turn be less than the connection timeout.\n\n- max_connections=NUMBER\n\n  Maximum number of connections to create at any given time.\n\n- enable_tls=[true|yes|1]\n\n  Enable opportunistic TLS with the forward host via `STARTTLS` (if the host advertises it).\n\n- auth_type=[plain|login]\n\n  Enable PLAIN or LOGIN SMTP AUTH. This is required to enable AUTH.\n\n- auth_user=USERNAME\n\n  SMTP AUTH username to use.\n\n- auth_pass=PASSWORD\n\n  SMTP AUTH password to use.\n\n- [tls]\n\nClient STARTTLS options are assembled by merging:\n\n1. `tls.ini` `[main]` — the global Haraka TLS config.\n2. `smtp_proxy.ini` `[tls]` — overrides. Anything set here wins.\n\nChanges to `tls.ini` require a Haraka restart to apply to the proxy path; changes to `smtp_proxy.ini` are picked up by the existing reload hook.\n","isInternal":false,"tokens":471,"sizeBytes":2090},{"name":"test.md","path":"docs/plugins/queue/test.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/queue/test.md","title":"Queue Skill","category":"anthropic-skill","format":"markdown","content":"# queue/test\n\nThis plugin saves incoming E-Mail to your temporary directory, as `mail_{message_id}.eml`, where message_id is a UUID.\n\nThis plugin can be useful to quickly test if you're able to receive incoming E-Mail and just dump them to disk.\n\nThe temporary directory is determined using Node's [`os.tmpdir()`](https://nodejs.org/api/os.html#ostmpdir), which respects standard platform configurations.\n","isInternal":false,"tokens":89,"sizeBytes":405},{"name":"rcpt_to.in_host_list.md","path":"docs/plugins/rcpt_to.in_host_list.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/rcpt_to.in_host_list.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# rcpt_to.in_host_list\n\nThis plugin is the mainstay of an inbound Haraka server. It should list the\ndomains that are local to the host. Mails that have RCPT TO not matching\na host in the given list will be passed onto other rcpt hooks. If no rcpt\nhook accepts the connection, it will be rejected.\n\n## Configuration\n\n- host_list\n\n  Specifies the list of hosts that are local to this server.\n\n- host_list_regex\n\n  Specifies the list of regexes that are local to this server. Note\n  all these regexes are anchored with ^regex$. One can choose not to\n  anchor with .\\*. There is the potential for bad regexes to be\n  too permissive if we don't anchor.\n\n- host_list.anti_spoof\n\n  When enabled, this will cause Haraka to reject any MAIL FROM where\n  the host appears within the host list but the connected host is not\n  a relay, e.g. connection.relaying is not set either by SMTP AUTH or\n  another plugin like 'relay'.\n\n## Relaying\n\nThis plugin checks to see if the MAIL FROM domain is local. When\nconnection.relaying is detected (haraka -h relay) and the MAIL FROM domain is\nlocal, this plugin will vouch for any RCPT. This limits relaying users to\nsending from local domains, which is much safer than letting relay clients\nsend from any domain.\n","isInternal":false,"tokens":303,"sizeBytes":1241},{"name":"rcpt_to.max_count.md","path":"docs/plugins/rcpt_to.max_count.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/rcpt_to.max_count.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# rcpt_to.max_count\n\nThe functionality of this plugin was integrated in to [haraka-plugin-limit](https://github.com/haraka/haraka-plugin-limit).\n","isInternal":false,"tokens":35,"sizeBytes":145},{"name":"record_envelope_addresses.md","path":"docs/plugins/record_envelope_addresses.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/record_envelope_addresses.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# record_envelope_addresses\n\nThis plugin adds two new header lines.\n\n- X-Envelope-To: the envelope RCPT TO address\n- X-Envelope-From: the envelope MAIL FROM address\n\nIt is useful if you need to know the exact addresses used to send an email, e.g. when\nthe email was sent to you as BCC or if it is a newsletter. In both cases the recipient\naddress is normally not recorded in the headers.\n\n## Caveats\n\nIf you enable this plugin you may introduce a possible information leak, i.e. disclosure\nof BCC recipients. So you never want to use this on an outgoing mail server and maybe also\nnot if this server is used as a relay.\n\n## Configuration\n\nThis plugin has no configuration.\n","isInternal":false,"tokens":155,"sizeBytes":673},{"name":"relay.md","path":"docs/plugins/relay.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/relay.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# relay\n\nRepackaged as [haraka-plugin-relay](https://github.com/haraka/haraka-plugin-relay).\n","isInternal":false,"tokens":28,"sizeBytes":93},{"name":"reseed_rng.md","path":"docs/plugins/reseed_rng.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/reseed_rng.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# reseed_rng\n\nReseeds `Math.random()` in each cluster worker at start-up using\n`crypto.randomBytes(256)`. Without this, workers forked at nearly the\nsame time can end up with correlated PRNG state, which can produce\nUUID collisions and other \"this should be impossible\" bugs.\n\nThe plugin relies on [seedrandom](https://www.npmjs.com/package/seedrandom)\nbeing loaded so that `Math.seedrandom()` is available.\n\nAnyone running with `nodes=...` in `smtp.ini` (i.e. cluster mode) should\nconsider enabling this plugin.\n\n## Configuration\n\nNo configuration.\n","isInternal":false,"tokens":131,"sizeBytes":550},{"name":"status.md","path":"docs/plugins/status.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/status.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# Status\n\nThis plugin allows to get internal status of queues and pools with SMTP commands sent from localhost.\n\n## Communication\n\n- **Request** &rarr; `STATUS <CMD> [param1] [param2]....`\n- **Response** &larr; _&lt;SMTP code 211 or 500>&lt;space>&lt;json encoded response>\\r\\n_\n\n### Example\n\n```\n< 220 example.com ESMTP Haraka ready\n> STATUS QUEUE INSPECT\n< 211 {\"delivery_queue\":[],\"temp_fail_queue\":[]}\n```\n\n## Available commands list\n\n- `STATUS POOL LIST` - map of active outbound connection pools, keyed by `host:port`\n- `STATUS QUEUE STATS` - queue statistics in format `\"<in_progress>/<delivery_queue length>/<temp_fail_queue length>\"`\n- `STATUS QUEUE LIST` - list of queue files on disk with _uuid, domain, mail_from, rcpt_to_ attributes\n- `STATUS QUEUE INSPECT` - returns merged content of `outbound.delivery_queue` and `outbound.temp_fail_queue` across all workers\n- `STATUS QUEUE DISCARD file` - stop delivering email file\n- `STATUS QUEUE PUSH file` - try to re-deliver email immediately\n\n## Notes\n\n### Live data only\n\n`POOL LIST`, `QUEUE STATS`, and `QUEUE INSPECT` reflect live in-memory state. They show only messages currently being processed or waiting in the retry queue. `QUEUE LIST` reads queue files from disk and may show messages that have already been delivered if they haven't been cleaned up yet.\n\n### Cluster mode\n\nIn cluster mode, `POOL LIST`, `QUEUE STATS`, and `QUEUE INSPECT` aggregate results from all worker processes into a single response:\n\n- `POOL LIST` — pool maps from all workers are merged into one object\n- `QUEUE STATS` — counters from all workers are summed into a single `\"N/N/N\"` string\n- `QUEUE INSPECT` — `delivery_queue` and `temp_fail_queue` arrays from all workers are concatenated\n\n`QUEUE LIST` always runs on the master process since it reads shared queue files from disk.\n","isInternal":false,"tokens":443,"sizeBytes":1830},{"name":"tarpit.md","path":"docs/plugins/tarpit.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/tarpit.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# tarpit\n\nThis plugin is designed to introduce deliberate delays on the response\nof every hook in order to slow down a connection. It has no\nconfiguration and is designed to be used only by other plugins.\n\nIt must be loaded early in config/plugins (e.g. before any plugins\nthat accept recipients or that return OK) but must be loaded _after_\nany plugins that wish to use it.\n\n## Usage\n\nTo use this plugin in another plugin set:\n\n    connection.notes.tarpit = <seconds to delay>;\n\nor\n\n    connection.transaction.notes.tarpit = <seconds to delay>;\n\n## Configuration\n\nThe configuration file for tarpit is config/tarpit.ini.\n\n- hooks_to_delay - a list of hooks to delay at. This setting can be used to\n  override the default list in the plugin. For example, if you notice that\n  malware is disconnecting after delaying rcpt_ok, you can remove just that\n  hook from the list:\n\nhooks_to_delay=connect,helo,ehlo,mail,rcpt,data,data_post,queue,unrecognized_command,vrfy,noop,rset,quit\n\n## Plugin Timeout\n\nconfig/tarpit.timeout (Default: 0)\n\nAll Haraka plugins can configure a _name_.timeout file. The timeout specifies\nhow long Haraka lets the plugin do nothing before it times out. When zero,\nthere is no timeout. When non-zero and _seconds to delay_ is longer than\ntarpit.timeout (default: 1s), you'll get errors like this in your log files:\n\n    [core] Plugin tarpit timed out on hook rcpt - make sure it calls the callback\n    [core] Plugin tarpit timed out on hook quit - make sure it calls the callback\n\nThe solution is to set the contents of config/tarpit.timeout to zero or\n**seconds to delay** + 1.\n\n## Logging\n\nWhen tarpitting a command it will log 'tarpitting response for Ns' to\nthe INFO facility where N is the number of seconds.\n","isInternal":false,"tokens":425,"sizeBytes":1735},{"name":"tls.md","path":"docs/plugins/tls.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/tls.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# tls\n\nThis plugin enables the use of TLS (via `STARTTLS`) in Haraka.\n\nFor this plugin to work you must have SSL certificates installed correctly.\n\nHaraka has [SNI](https://en.wikipedia.org/wiki/Server_Name_Indication) support. When the remote MUA/MTA presents a servername during the TLS handshake and a TLS certificate with that Common Name matches, that certificate will be presented. If no match is found, the default certificate (see Certificate Files) is presented.\n\n## Certificate Files\n\nDefaults settings are shown and can be overridden in `config/tls.ini`.\n\n```ini\nkey=tls_key.pem\ncert=tls_cert.pem\ndhparam=dhparams.pem\n```\n\n## Certificate Directory\n\nIf the directory `config/tls` exists, files within the directory are PEM encoded TLS files in one of two formats: bundles or Wild Wild West.\n\n### Certificate bundles\n\nGenerate PEM bundles in The Usual Way[TM] by concatenating the key, certificate, and CA/chain certs in that order. Example:\n\n```sh\ncat example.com.key example.com.crt ca-int.crt > haraka/config/tls/example.com.pem\n```\n\nAn example [acme.sh](https://acme.sh) deployment [script](https://github.com/msimerson/Mail-Toaster-6/blob/master/provision/letsencrypt.sh) installs [Let's Encrypt](https://letsencrypt.org) certificate bundles to the Haraka `config/tls`directory.\n\n### Wild Wild West\n\nPEM encoded TLS certificates and keys can be stored in files in `config/tls`. The certificate loader is recursive, so TLS files can be in subdirs like `config/tls/mx1.example.com`. The certificate names are parsed from the 1st cert in each file and indexed by the certs Common Name(s). Subject Alternate Names are supported. The file name containing the certificates does _not_ matter. Additional certificates within each file are presumed to be CA chain (intermediate) certificates.\n\nIf the TLS key is stored in the same file as the matching certificate, then the name of the file does not matter. If the TLS key is alone in a file, the file MUST be named with the keys Common Name. The file extension does not matter, `.pem` and `.key` are common. If the key is used for multiple CNs, the key must be stored in a file name matching each CN. Examples of working TLS key/cert file pairs for the Common Name mx1.example.com:\n\n1. certificate bundle (see above), key & cert in same file\n   - config/tls/mx1.example.com.pem (recommended)\n   - config/tls/any-unique-name.pem (CN is extracted from 1st cert)\n2. files in TLS dir\n   - config/tls/mx1.example.com.crt\n   - config/tls/mx1.example.com.key\n3. files in subdir\n   - config/tls/example.com/mx1.cert\n   - config/tls/example.com/mx1.example.com.key\n4. wildcard bundle on Windows platform (\\* is not allowed in file names)\n   - config/tls/\\_.example.com.pem\n\n## Purchased Certificate\n\nFor purchased certificate, append any intermediate/chained/ca-cert files to the certificate in this order:\n\n1. The CA signed SSL cert\n2. Any intermediate certificates\n3. The CA root certificate\n\nSee also [Setting Up TLS](https://github.com/haraka/Haraka/wiki/Setting-up-TLS-with-CA-certificates)\n\n## Self Issued (unsigned) Certificate\n\nCreate a certificate and key file in the config directory with the following command:\n\n    openssl req -x509 -nodes -days 2190 -newkey rsa:2048 \\\n            -keyout config/tls_key.pem -out config/tls_cert.pem\n\nYou will be prompted to provide details of your organization. Make sure the\nCommon Name is set to your servers Fully Qualified Domain Name, which should\nbe the same as the contents of your `config/me` file.\n\n## Configuration\n\nThe following settings can be specified in `config/tls.ini`.\n\n### key\n\nSpecifies an alternative location for the key file. For multiple keys, use `key[]=` assignment for each. Non-absolute paths are relative to the `config/` directory.\n\nTo configure a single key and a cert chain, located in the `config/`\ndirectory, use the following in `tls.ini`:\n\n```ini\nkey=example.com.key.pem\ncert=example.com.crt-chain.pem\n```\n\nTo use multiple pairs of key and cert chain files outside of the haraka\n`config/` directory, configure instead:\n\n```ini\nkey[]=/etc/ssl/private/example.com.rsa.key.pem\ncert[]=/etc/ssl/private/example.com.rsa.crt-chain.pem\nkey[]=/etc/ssl/private/example.com.ecdsa.key.pem\ncert[]=/etc/ssl/private/example.com.ecdsa.crt-chain.pem\n```\n\n### cert\n\nSpecifies the location(s) for the certificate chain file. For multiple certificate chains, use `cert[]=` assignment for each. Non-absolute paths are relative to the `config/` directory. See the description of the `key` parameter for specific use.\n\n### no_tls_hosts\n\nIf needed, add this section to the `config/tls.ini` file and list any IP ranges that have broken TLS hosts. Ex:\n\n```ini\n[no_tls_hosts]\n192.168.1.3\n172.16.0.0/16\n```\n\nNote: `[no_tls_hosts]` section applies to inbound only. For outbound mail, this feature is implemented as an array like `force_tls_hosts`:\n\n```ini\n[outbound]\nno_tls_hosts[]=192.168.1.3\nno_tls_hosts[]=172.16.0.0/16\n```\n\nThe [Node.js TLS](http://nodejs.org/api/tls.html) page has additional information about the following options.\n\n### no_starttls_ports\n\nAn array of incoming ports on which Haraka will not advertise STARTTLS capability.\n\n```ini\nno_starttls_ports[]=2525\n```\n\n### force_tls_hosts\n\nFor known good TLS hosts, it's possible to force that the outbound mailer will only connect via secure sockets. This makes Haraka use _forced TLS_ instead of _opportunistic TLS_. For forced TLS, the STARTTLS upgrade must succeed with a valid certificate (overriding `rejectUnauthorized`). The list is matched both against the host (MX record or `nexthop` in `relay_dest_domains.ini`), and the domain name of the email address.\n\nNote: unlike `no_tls_hosts`, this feature is implemented as an array:\n\n```ini\n[outbound]\nforce_tls_hosts[]=172.17.123.1\nforce_tls_hosts[]=172.17.124.0/24\nforce_tls_hosts[]=mx.example.org\nforce_tls_hosts[]=example.com\n```\n\n### ciphers\n\nA list of allowable ciphers to use. Example:\n\n    ciphers=EECDH+AESGCM:EDH+aRSA+AESGCM:EECDH+AES256:EDH+aRSA+AES256:EECDH+AES128:EDH+aRSA+AES128:RSA+AES:RSA+3DES\n\nSee also: [Mozilla SSL configuration generator](https://ssl-config.mozilla.org/) and the [SSLlabs Test Page](https://www.ssllabs.com/ssltest/index.html)\n\n### minVersion\n\nSpecifies minimum allowable TLS protocol version to use. Example:\n\n     minVersion=TLSv1.1\n\nIf unset, the default is Node's `tls.DEFAULT_MIN_VERSION` constant\n(currently `'TLSv1.2'`). Valid values: `'TLSv1.3'`, `'TLSv1.2'`,\n`'TLSv1.1'`, `'TLSv1'`.\n\n### honorCipherOrder\n\nIf specified, the list of configured ciphers is treated as the cipher priority from highest to lowest. The first matching cipher will be used, instead of letting the client choose. The default is `true`.\n\n### ecdhCurve\n\nSpecifies the elliptic curve used for ECDH or ECDHE ciphers.\nOnly one curve can be specified. The default is `prime256v1` (NIST P-256).\n\n### dhparam\n\nSpecifies the file containing the diffie-hellman parameters to use for DH or DHE key exchange. If this param or file is missing, it will be generated automatically. Default: `dhparams.pem`.\n\n### requestCert\n\nWhether Haraka should request a certificate from a connecting client.\n\n    requestCert=[true|false]  (default: true)\n\n### rejectUnauthorized\n\nReject connections from clients without a CA validated TLS certificate.\n\n    rejectUnauthorized=[true|false]  (default: false)\n\n### requireAuthorized\n\nWhen `rejectUnauthorized=false`, require validated TLS certificates on just the specified ports.\n\n```ini\nrequireAuthorized[]=465\n;requireAuthorized[]=587\n```\n\n### secureProtocol\n\nLegacy. Specifies the OpenSSL API function used to negotiate TLS — see\nthe [OpenSSL API page](https://www.openssl.org/docs/manmaster/ssl/ssl.html).\nPrefer `minVersion` for modern setups; `secureProtocol` is only useful\nto lock to a specific historic protocol.\n\n### requestOCSP\n\nSpecifies that OCSP Stapling should be enabled, according to RFC 6066.\nStapling of OCSP messages allows the client to receive these along the\nTLS session setup instead of delaying the session setup by requiring a\nseparate http connection to the OCSP server.\n\n    requestOCSP=[true|false]  (default: false)\n\nOCSP responses from the OCSP server are cached in memory for as long as\nthey are valid, and get refreshed after that time. A server restart\nrequires the OCSP responses to be fetched again upon the first client\nconnection.\n\n## Inbound Specific Configuration\n\nBy default the above options are shared with outbound mail (either\nusing `smtp_forward`, `smtp_proxy` or plain outbound mail heading to\nan external destination). To make these options specific to inbound\nmail, put them under an `[inbound]` parameter group. Outbound options\ncan go under an `[outbound]` parameter group, and plugins that use\nSMTP tls for queueing such as `smtp_proxy` and `smtp_forward` can\nuse that plugin name for plugin specific options.\n\n## `[redis]` section\n\nThis section is mainly used to enable so called _TLS NO-GO_ feature that essentially stops advertising/using TLS if there was a problem setting it up previously. We use `no_tls|ip.add.re.ss` key to store the flag in redis. There are a couple of settings that control the behavior:\n\n`disable_for_failed_hosts = true` to enable the feature\n\n`disable_expiry = 604800` to set for how long we disable TLS for failing host, in seconds\n\n`disable_inbound_expiry = 3600` same as above, but applies to inbound (aka STARTTLS capability) only\n","isInternal":false,"tokens":2295,"sizeBytes":9379},{"name":"toobusy.md","path":"docs/plugins/toobusy.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/toobusy.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# toobusy\n\nThis plugin will stop Haraka accepting new connections when the event loop\nlatency is too high.\n\nSee https://github.com/STRML/node-toobusy for details.\n\nTo use this plugin you must install the [`toobusy-js`](https://www.npmjs.com/package/toobusy-js)\nmodule — it is not bundled with Haraka. From your Haraka install\ndirectory:\n\n```sh\nnpm install toobusy-js\n```\n\nThis plugin registers on the `connect` hook with priority `-100`, so it\nruns ahead of other `connect`/`lookup_rdns` plugins. Listing it near the\ntop of `config/plugins` is still a good idea for clarity.\n\n## Configuration\n\nIf you wish to override the default maxLag value of 70ms then add the desired\nvalue to config/toobusy.maxlag. This can be set and changed at runtime and\nno restart is required.\n\nNote that if you set the maxLag value to <10 then this will cause the toobusy\nmodule to raise an exception which will cause Haraka to stop.\n","isInternal":false,"tokens":237,"sizeBytes":914},{"name":"xclient.md","path":"docs/plugins/xclient.md","rawUrl":"https://raw.githubusercontent.com/haraka/Haraka/HEAD/docs/plugins/xclient.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"# xclient\n\nImplements the [XCLIENT](http://www.postfix.org/XCLIENT_README.html) protocol.\n\n## configuration\n\n- xclient.hosts\n\n  A list of IP addresses, one per line that should be allowed to use the\n  XCLIENT protocol. Localhost (127.0.0.1 or ::1) is allowed implicitly.\n","isInternal":false,"tokens":75,"sizeBytes":271}],"systemPromptSnippet":"<yakaai_skills repo=\"haraka/Haraka\">\n<!-- File: AGENTS.md (Tokens: ~2183 | Category: root-instruction) -->\n<!-- agents-version: 3 -->\n\n# AGENTS.md\n\nShared instructions for every coding agent in this repo. The tool-specific files\n(`CLAUDE.md`, `GEMINI.md`, `.github/copilot-instructions.md`) defer to this one.\nIf a package-level instruction file exists, it is authoritative for that package.\n\n## Repository model\n\n- This file is shared across every Haraka repository. It describes the whole family — not just this repo. Each repo is an independent npm package, there is no root workspace. Run every command from the package root.\n- Repos are worked on both standalone and as sibling checkouts in a combined tree (`Haraka/`, `plugin/<name>/`, …). Don't assume sibling packages are present on disk.\n- The family:\n  - `Haraka` — core SMTP server (has `./run_tests`).\n  - `haraka-plugin-<name>` — optional plugins; may depend on each other (e.g. bounce → spf).\n  - `haraka-config` — config loader with hot-reload.\n  - `haraka-results`, `haraka-notes` — per-connection result / note tracking.\n  - `haraka-net-utils`, `haraka-utils`, `haraka-constants`, `haraka-dsn`, `haraka-tld`, `haraka-message-stream` — shared libraries.\n  - `@haraka/email-address` — the RFC 5321/5322 parser (supersedes the deprecated `address-rfc282x`).\n  - `@haraka/eslint-config`, `haraka-test-fixtures`.\n\n## Working agreement\n\n- Do only what was asked. When you spot an adjacent bug or smell, surface it and ask before expanding scope. Don't silently refactor, and don't ignore it.\n- Preserve compatibility; break it only for an explicit, stated reason.\n- For protocol behavior, identify the relevant RFC and verify conformance against the existing implementation.\n\n## Source control\n\n- Never run history- or remote-mutating commands (`git commit`, `git push`, `git tag`, `gh pr create`) unless explicitly asked.\n- create diffs; the human reviews, commits, and pushes.\n- No trailers on commit messages or PR bodies.\n- When asked to generate a PR:\n  - keep it succinct, terse, and DRY\n  - Post a link to it in the console\n- Document changes relative to main in CHANGELOG.md\n  - one clear concise entry per change, < 50 chars\n  - prefer Conventional Commit format, imperative mood.\n  - Rationale belongs in the code or PR, not the bullet.\n\n## Coding standards\n\n- Target current Node LTS; prefer ES2024 over legacy patterns.\n- Existing code is CommonJS (`require`/`exports`) — match it. New modules should use ESM with CJS interop (see `@haraka/email-address`).\n- Add `node:` prefixes to built-in requires in any file you touch (`require('fs')` → `require('node:fs')`).\n- Prefer: promise APIs (`fs/promises`), `for...of`/`for...in` over `forEach`, `node:readline` for line parsing, template literals over concatenation, `true`/`false` over `1`/`0`, and guard-style early returns.\n- Remove commented-out code (it lives in git history). `npm run qlty` must pass without warnings.\n\n## Concision\n\n- Don't Repeat Yourself: Say each thing exactly once, in the place it belongs.\n- Sentences: must carry non-obvious facts the reader cannot get from the surroundings.\n- Cut scaffolding, keep facts.\n\nNever write these:\n\n- **Announcements.** \"One question:\", \"Two reasons:\", \"The point is:\", \"Here's the thing:\". Say the thing. If you announce a count, the count must be right — so don't announce one.\n- **Justification tails.** A clause after a comma or dash that argues for the clause before it: \"…and a guess there is worse than none\", \"…which is the failure this exists to prevent\".\n- **The contrast tic.** \"X, not Y\" and \"X rather than Y\" where Y is obvious. State X.\n- **Clauses doing an adjective's job.** \"a setting nobody has written\" is \"an unwritten setting\"; \"a copy nobody has opened\" is \"an unopened copy\"; \"still has to\" is \"must\". A state with a name takes the name.\n- **Closing morals.** A final sentence restating the paragraph's point.\n\n## Comments\n\n- Keep only WHY comments — a hidden constraint, an invariant, a workaround for a specific bug, or an RFC citation that explains otherwise-surprising behavior.\n- Delete WHAT comments that restate the code.\n- When a rename makes a comment redundant, delete it.\n- Never narrate history or audit findings.\n- Never count the code — counts rot.\n- Prefer self-documenting code: a better name beats any comment.\n\n## Haraka plugins\n\n- Full hook/API reference: `docs/Plugins.md` in the `Haraka` core repo.\n- A plugin is an npm package: `index.js` (`exports.register` + hook handlers), `config/` (default `.ini`/`.json`/`.yaml`), `test/`, `README.md`.\n- Register hooks in `exports.register` with `this.register_hook('phase', 'method'[, priority])`.\n- Hook handlers take `(next, connection)` (rcpt hooks also take `rcpt`) and must call `next` exactly once. Gate early — return `next()` on missing transaction, disabled config, or skip conditions. Signal a verdict with `next(DENY|DENYSOFT|OK, msg)`; `DENY`/`OK`/etc. are plugin-scope globals (no import).\n- Results: `connection.transaction.results.add(this, { pass|fail|skip|msg|err, emit })`; query with `results.has(plugin, list, search)`. `emit: true` already logs the collated line — don't also `loginfo`/`logerror` the same thing. results.add(this, {err}) always logs.\n- Config loads via `config.get` with a hot-reload callback; declare every boolean or it stays a string and `=== true/false` silently fails:\n  ```js\n  this.cfg = this.config.get('name.ini', { booleans: ['+a.b', '-c.d'] }, () => this.load())\n  ```\n- Keep handlers thin. Push pure decision logic and I/O into `lib/*.js` as pure functions that return a verdict/value; the handler just maps that to `results.add` + `next`. For external I/O (DNS, network), expose an injectable seam — a swappable function whose default is the real implementation — so tests run without mocks.\n- If you add files outside `index.js` (e.g. a `lib/` dir), add them to `package.json` `files` so they publish.\n\n## Testing\n\n- Test real behavior and observable outcomes — `results`, return codes, emitted headers, side effects — not how a function was called. Asserting call shape (`calledWith`, arity, call counts) tests the test and hides signature drift.\n- Mocks/stubs are a smell. Prefer real inputs; when you must isolate a dependency, inject a seam and assert the outcome. Never leave a stub that neuters the path under test — that yields green tests proving nothing.\n- For bug fixes, add a failing test first, then fix.\n- Every feature ships with meaningful tests. A `.skip` is a coverage hole: fix it or delete it.\n- Use `node:test` and `node:assert/strict` for new tests and Mocha migrations. Plugin tests use `haraka-test-fixtures` (`makePlugin`, `makeConnection`, `callHook`).\n- Run the package's `lint`, `prettier`, and `format` before handoff.\n\n## Commands (run inside the target package)\n\n- Test: `npm test`. Single file: `node --test test/path/to/file.js`.\n- Haraka core repo only: `./run_tests [test/plugins/foo.js]`.\n- Coverage: `npm run test:coverage`; lcov: `npm run test:coverage:lcov`. Keep coverage at/above ~90%.\n- If coverage output includes non-source files (e.g. `package.json`, `test/*`), scope it with `--test-coverage-include` (preferred when the list is short) or `--test-coverage-exclude`.\n- Lint/format: `npm run lint` / `prettier` / `format`. Version drift: `npm run versions[:fix]`.\n\n## Package script parity\n\n- node:test packages should expose `test`, `test:coverage`, `test:coverage:lcov`, `lint`, `prettier`, `format` with matching shapes across siblings. Standardize on node:test coverage (not c8); add the canonical scripts when touching a package that lacks them:\n  ```jsonc\n  \"test:coverage\": \"node --test --experimental-test-coverage\",\n  \"test:coverage:lcov\": \"mkdir -p coverage && node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info\"\n  ```\n\n## Repo badges\n\n- Code climate is deprecated. Update with qlty.sh instead.\n- The canonical format for badges should be:\n  - Top of README.md:\n    - [![Test][ci-img]][ci-url] [![Cover][cov-img]][cov-url] [![Qlty][qlty-img]][qlty-url]\n  - Bottom of README.md:\n    - [ci-img]: https://github.com/haraka/<name>/actions/workflows/ci.yml/badge.svg\n    - [ci-url]: https://github.com/haraka/<name>/actions/workflows/ci.yml\n    - [cov-img]: https://codecov.io/github/haraka/<name>/coverage.svg\n    - [cov-url]: https://codecov.io/github/haraka/<name>\n    - [qlty-img]: https://qlty.sh/gh/haraka/projects/<name>/maintainability.svg\n    - [qlty-url]: https://qlty.sh/gh/haraka/projects/<name>\n\n<!-- File: CLAUDE.md (Tokens: ~5 | Category: claude-rule) -->\nsee AGENTS.md\n\n<!-- File: .github/copilot-instructions.md (Tokens: ~2183 | Category: copilot-instructions) -->\n<!-- agents-version: 3 -->\n\n# AGENTS.md\n\nShared instructions for every coding agent in this repo. The tool-specific files\n(`CLAUDE.md`, `GEMINI.md`, `.github/copilot-instructions.md`) defer to this one.\nIf a package-level instruction file exists, it is authoritative for that package.\n\n## Repository model\n\n- This file is shared across every Haraka repository. It describes the whole family — not just this repo. Each repo is an independent npm package, there is no root workspace. Run every command from the package root.\n- Repos are worked on both standalone and as sibling checkouts in a combined tree (`Haraka/`, `plugin/<name>/`, …). Don't assume sibling packages are present on disk.\n- The family:\n  - `Haraka` — core SMTP server (has `./run_tests`).\n  - `haraka-plugin-<name>` — optional plugins; may depend on each other (e.g. bounce → spf).\n  - `haraka-config` — config loader with hot-reload.\n  - `haraka-results`, `haraka-notes` — per-connection result / note tracking.\n  - `haraka-net-utils`, `haraka-utils`, `haraka-constants`, `haraka-dsn`, `haraka-tld`, `haraka-message-stream` — shared libraries.\n  - `@haraka/email-address` — the RFC 5321/5322 parser (supersedes the deprecated `address-rfc282x`).\n  - `@haraka/eslint-config`, `haraka-test-fixtures`.\n\n## Working agreement\n\n- Do only what was asked. When you spot an adjacent bug or smell, surface it and ask before expanding scope. Don't silently refactor, and don't ignore it.\n- Preserve compatibility; break it only for an explicit, stated reason.\n- For protocol behavior, identify the relevant RFC and verify conformance against the existing implementation.\n\n## Source control\n\n- Never run history- or remote-mutating commands (`git commit`, `git push`, `git tag`, `gh pr create`) unless explicitly asked.\n- create diffs; the human reviews, commits, and pushes.\n- No trailers on commit messages or PR bodies.\n- When asked to generate a PR:\n  - keep it succinct, terse, and DRY\n  - Post a link to it in the console\n- Document changes relative to main in CHANGELOG.md\n  - one clear concise entry per change, < 50 chars\n  - prefer Conventional Commit format, imperative mood.\n  - Rationale belongs in the code or PR, not the bullet.\n\n## Coding standards\n\n- Target current Node LTS; prefer ES2024 over legacy patterns.\n- Existing code is CommonJS (`require`/`exports`) — match it. New modules should use ESM with CJS interop (see `@haraka/email-address`).\n- Add `node:` prefixes to built-in requires in any file you touch (`require('fs')` → `require('node:fs')`).\n- Prefer: promise APIs (`fs/promises`), `for...of`/`for...in` over `forEach`, `node:readline` for line parsing, template literals over concatenation, `true`/`false` over `1`/`0`, and guard-style early returns.\n- Remove commented-out code (it lives in git history). `npm run qlty` must pass without warnings.\n\n## Concision\n\n- Don't Repeat Yourself: Say each thing exactly once, in the place it belongs.\n- Sentences: must carry non-obvious facts the reader cannot get from the surroundings.\n- Cut scaffolding, keep facts.\n\nNever write these:\n\n- **Announcements.** \"One question:\", \"Two reasons:\", \"The point is:\", \"Here's the thing:\". Say the thing. If you announce a count, the count must be right — so don't announce one.\n- **Justification tails.** A clause after a comma or dash that argues for the clause before it: \"…and a guess there is worse than none\", \"…which is the failure this exists to prevent\".\n- **The contrast tic.** \"X, not Y\" and \"X rather than Y\" where Y is obvious. State X.\n- **Clauses doing an adjective's job.** \"a setting nobody has written\" is \"an unwritten setting\"; \"a copy nobody has opened\" is \"an unopened copy\"; \"still has to\" is \"must\". A state with a name takes the name.\n- **Closing morals.** A final sentence restating the paragraph's point.\n\n## Comments\n\n- Keep only WHY comments — a hidden constraint, an invariant, a workaround for a specific bug, or an RFC citation that explains otherwise-surprising behavior.\n- Delete WHAT comments that restate the code.\n- When a rename makes a comment redundant, delete it.\n- Never narrate history or audit findings.\n- Never count the code — counts rot.\n- Prefer self-documenting code: a better name beats any comment.\n\n## Haraka plugins\n\n- Full hook/API reference: `docs/Plugins.md` in the `Haraka` core repo.\n- A plugin is an npm package: `index.js` (`exports.register` + hook handlers), `config/` (default `.ini`/`.json`/`.yaml`), `test/`, `README.md`.\n- Register hooks in `exports.register` with `this.register_hook('phase', 'method'[, priority])`.\n- Hook handlers take `(next, connection)` (rcpt hooks also take `rcpt`) and must call `next` exactly once. Gate early — return `next()` on missing transaction, disabled config, or skip conditions. Signal a verdict with `next(DENY|DENYSOFT|OK, msg)`; `DENY`/`OK`/etc. are plugin-scope globals (no import).\n- Results: `connection.transaction.results.add(this, { pass|fail|skip|msg|err, emit })`; query with `results.has(plugin, list, search)`. `emit: true` already logs the collated line — don't also `loginfo`/`logerror` the same thing. results.add(this, {err}) always logs.\n- Config loads via `config.get` with a hot-reload callback; declare every boolean or it stays a string and `=== true/false` silently fails:\n  ```js\n  this.cfg = this.config.get('name.ini', { booleans: ['+a.b', '-c.d'] }, () => this.load())\n  ```\n- Keep handlers thin. Push pure decision logic and I/O into `lib/*.js` as pure functions that return a verdict/value; the handler just maps that to `results.add` + `next`. For external I/O (DNS, network), expose an injectable seam — a swappable function whose default is the real implementation — so tests run without mocks.\n- If you add files outside `index.js` (e.g. a `lib/` dir), add them to `package.json` `files` so they publish.\n\n## Testing\n\n- Test real behavior and observable outcomes — `results`, return codes, emitted headers, side effects — not how a function was called. Asserting call shape (`calledWith`, arity, call counts) tests the test and hides signature drift.\n- Mocks/stubs are a smell. Prefer real inputs; when you must isolate a dependency, inject a seam and assert the outcome. Never leave a stub that neuters the path under test — that yields green tests proving nothing.\n- For bug fixes, add a failing test first, then fix.\n- Every feature ships with meaningful tests. A `.skip` is a coverage hole: fix it or delete it.\n- Use `node:test` and `node:assert/strict` for new tests and Mocha migrations. Plugin tests use `haraka-test-fixtures` (`makePlugin`, `makeConnection`, `callHook`).\n- Run the package's `lint`, `prettier`, and `format` before handoff.\n\n## Commands (run inside the target package)\n\n- Test: `npm test`. Single file: `node --test test/path/to/file.js`.\n- Haraka core repo only: `./run_tests [test/plugins/foo.js]`.\n- Coverage: `npm run test:coverage`; lcov: `npm run test:coverage:lcov`. Keep coverage at/above ~90%.\n- If coverage output includes non-source files (e.g. `package.json`, `test/*`), scope it with `--test-coverage-include` (preferred when the list is short) or `--test-coverage-exclude`.\n- Lint/format: `npm run lint` / `prettier` / `format`. Version drift: `npm run versions[:fix]`.\n\n## Package script parity\n\n- node:test packages should expose `test`, `test:coverage`, `test:coverage:lcov`, `lint`, `prettier`, `format` with matching shapes across siblings. Standardize on node:test coverage (not c8); add the canonical scripts when touching a package that lacks them:\n  ```jsonc\n  \"test:coverage\": \"node --test --experimental-test-coverage\",\n  \"test:coverage:lcov\": \"mkdir -p coverage && node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info\"\n  ```\n\n## Repo badges\n\n- Code climate is deprecated. Update with qlty.sh instead.\n- The canonical format for badges should be:\n  - Top of README.md:\n    - [![Test][ci-img]][ci-url] [![Cover][cov-img]][cov-url] [![Qlty][qlty-img]][qlty-url]\n  - Bottom of README.md:\n    - [ci-img]: https://github.com/haraka/<name>/actions/workflows/ci.yml/badge.svg\n    - [ci-url]: https://github.com/haraka/<name>/actions/workflows/ci.yml\n    - [cov-img]: https://codecov.io/github/haraka/<name>/coverage.svg\n    - [cov-url]: https://codecov.io/github/haraka/<name>\n    - [qlty-img]: https://qlty.sh/gh/haraka/projects/<name>/maintainability.svg\n    - [qlty-url]: https://qlty.sh/gh/haraka/projects/<name>\n\n<!-- File: docs/plugins/aliases.md (Tokens: ~28 | Category: anthropic-skill) -->\n# aliases\n\nRepackaged as [haraka-plugin-aliases](https://github.com/haraka/haraka-plugin-aliases).\n\n<!-- File: docs/plugins/auth/auth_bridge.md (Tokens: ~309 | Category: anthropic-skill) -->\n# auth/auth_bridge\n\nThis plugin allows you to authenticate users to remote SMTP servers\nbridging the original user and password to the remote server,\nand proxy the result back to authenticate the client.\n\nThis plugin is meant to be used with the plugin `queue/smtp_bridge`.\n\nIt is different than `auth/auth_proxy` because it doesn't require\nthe AUTH user in user@domain.com format, and it doesn't check that\nthe domain is the configuration file. This plugins simply takes\nthe original user and password and tries to authenticate it in the\nremote SMTP server.\n\n## Configuration\n\nConfiguration is stored in `config/smtp_bridge.ini` and uses the INI\nstyle formatting.\n\nThe configuration of this plugin is simple:\n\n    host=localhost\n    #port=\n    #auth_type=\n    #priority=10\n\n- host: the host where you will be authenticating and posting,\n  for example `smtp.host.tld`, `192.0.2.25:587`, or `[2001:db8::1]:587`.\n  This is the only setting required. See [Endpoint][endpoint] for the\n  accepted forms. An IPv6 address must be bracketed to include a port.\n\nIf needed you can also set\n\n- port: used when `host` doesn't include one. Defaults to 25.\n\n[endpoint]: https://github.com/haraka/haraka-net-utils#endpoint\n\nThe options `auth_type` and `priority` will be used by `queue/smtp_bridge`\n\n<!-- File: docs/plugins/auth/auth_ldap.md (Tokens: ~56 | Category: anthropic-skill) -->\n# auth/auth_ldap\n\nRepackaged as [haraka-plugin-auth-ldap](https://github.com/haraka/haraka-plugin-auth-ldap).\nLoading `auth/auth_ldap` in `config/plugins` is auto-redirected to `auth-ldap`.\n\n<!-- File: docs/plugins/auth/auth_proxy.md (Tokens: ~356 | Category: anthropic-skill) -->\n# auth/auth_proxy\n\nThis plugin allows you to authenticate users by domain to remote SMTP servers\nand proxy the result back to authenticate the client.\n\nFor this to work - the AUTH username _must_ be in user@domain.com format\nregardless as to whether the remote SMTP server requires it in this format.\nThe domain part of the username is used to look-up which SMTP servers should\nbe used to authenticate users for that domain.\nWhen sending the AUTH credentials to the remote server, this plugin will try\nand send the full username e.g. user@domain.com first and if this fails it\nwill then strip the @domain.com part and just send the unqualified username.\n\nDue to the way this plugin works - it can only support PLAIN and LOGIN\nauthentication methods and for this reason it requires that STARTTLS be\nused via the tls plugin before it will advertise AUTH capabilities by the\nEHLO command. When connecting to the remote SMTP systems it will always\nattempt STARTTLS if it is offered, but it does _not_ require it, so caution\nshould be exercised.\n\n## Configuration\n\nConfiguration is stored in `config/auth_proxy.ini` and uses the INI\nstyle formatting.\n\nThe configuration of this plugin is simple:\n\n    [domains]\n    domain.com = server1.domain.com:587 server2.domain.com\n\nWhere domain.com is the domain-part of the username equals a list of hosts\nthat should be consulted in host:port format. The :port is optional and will\ndefault to 25. The list of hosts can be space, semi-colon or comma separated.\n\nIf more than host is specified, then subsequent hosts will only be tested if\nthere is some sort of error e.g. timeout, connection or protocol error.\n\n<!-- File: docs/plugins/auth/auth_vpopmaild.md (Tokens: ~360 | Category: anthropic-skill) -->\n# auth/auth_vpopmaild\n\nThe `auth/vpopmaild` plugin allows SMTP users to authenticate against a vpopmaild daemon.\n\n## Configuration\n\nThe configuration file is stored in `config/auth_vpopmaild.ini`.\n\n### settings\n\n- host: The host/IP that vpopmaild is listening on (default: localhost).\n\n- port: The TCP port that vpopmaild is listening on (default: 89).\n\n- sysadmin: A colon separated username:password of a vpopmail user with SYSADMIN privileges (see vpopmail/bin/vmoduser -S). This is **only** necessary to support CRAM-MD5 which requires access to the clear text password. On new installs, it's best not to use CRAM-MD5, as it requires storing clear text passwords. Legacy clients with MUAs configured to authenticate with CRAM-MD5 will need this enabled.\n\n- constrain_sender: (default: true). For outbound messages (due to successful AUTH), constrain the envelope sender (MAIL FROM) to the same domain as the authenticated user. This setting, combined with `rate_rcpt_sender` in the [limit](https://github.com/haraka/haraka-plugin-limit) plugin can dramatically reduce the amount of backscatter and spam sent when an email account is compromised.\n\n### Per-domain Configuration\n\nAdditionally, domains can each have their own configuration for connecting\nto vpopmaild. The defaults are the same, so only the differences needs to\nbe declared. Example:\n\n```ini\n[example.com]\nhost=192.168.0.1\nport=999\n\n[example2.com]\nhost=192.168.0.2\nsysadmin=postmaster@example2.com:sekret\n```\n\n<!-- File: docs/plugins/auth/flat_file.md (Tokens: ~401 | Category: anthropic-skill) -->\n# auth/flat_file\n\nThe `auth/flat_file` plugin allows you to create a file containing username and password combinations, and have relaying users authenticate from that file.\n\nNote that passwords are stored in clear-text, so this may not be a great idea for large scale systems. However the plugin would be a good start for someone looking to implement authentication using some other form of auth.\n\n**Security** - it is recommended to switch to [auth-encfile][url-authencflat] to protect your user credentials.\n\n**IMPORANT NOTE** - this plugin requires that STARTTLS be used via the tls plugin before it will advertise AUTH capabilities by the EHLO command. Localhost and IPs in RFC1918 ranges\nare exempt from this rule.\n\n## Configuration\n\nConfiguration is stored in `config/auth_flat_file.ini`.\n\n- [core]methods\n\nAuthentication methods are listed in the `[core]methods` parameter. Authentification methods are comma separated. Currently supported methods are: `CRAM-MD5`, `PLAIN` and `LOGIN`. The `PLAIN` and `LOGIN` methods are insecure and require TLS to be enabled.\n\n- [core]constrain_sender: (default: true). For outbound messages (due to successful AUTH), constrain the envelope sender (MAIL FROM) to the same domain as the authenticated user. This setting, combined with `rate_rcpt_sender` in the [limit](https://github.com/haraka/haraka-plugin-limit) plugin can dramatically reduce the amount of backscatter and spam sent when an email account is compromised.\n\nExample:\n\n```ini\n[core]\nmethods=PLAIN,LOGIN,CRAM-MD5\nconstrain_sender=true\n```\n\nUsers are stored in the `[users]` section.\n\nExample:\n\n```ini\n[users]\nuser1=password1\nuser@domain.com=password2\n```\n\n[url-authencflat]: https://github.com/AuspeXeu/haraka-plugin-auth-enc-file\n\n<!-- File: docs/plugins/block_me.md (Tokens: ~159 | Category: anthropic-skill) -->\n# block_me\n\nThis plugin allows you to configure an address which mail sent to will be\nparsed for a From: address in the body of the message, and will add that\nfrom address to the `mail_from.blocklist` config file.\n\nEffectively this allows your users to forward spams that got through to a\nparticular mailbox to block them in the future.\n\nNote that this is a system-wide block, and not per-user. Be careful with this.\n\n## Configuration\n\n- `config/block_me.recipient` - a file containing the address to email to\n  get something blocked. For example: **spam@domain.com**.\n\n- `config/block_me.senders` - a file containing a list of email addresses\n  that are allowed to email the dropbox.\n\n<!-- File: docs/plugins/data.signatures.md (Tokens: ~69 | Category: anthropic-skill) -->\n# data.signatures\n\nThis plugin allows you to add string signatures to a configuration file and\nhave this plugin scan the body text of an email for those strings. Mails\nmatching these signatures will be blocked.\n\n## Configuration\n\n- data.signatures\n\n  This file contains a list of strings (one per line) that will be matched.\n\n<!-- File: docs/plugins/delay_deny.md (Tokens: ~158 | Category: anthropic-skill) -->\n# delay_deny\n\nDelays all pre-DATA 'deny' results until the recipients are sent\nand all post-DATA commands until all hook_data_post plugins have run.\nThis allows relays and authenticated users to bypass pre-DATA rejections.\n\n## Configuration\n\nConfiguration options are in config/delay_deny.ini.\n\nThis plugin operates in one of two modes: included and excluded.\n\n### included plugins\n\nA comma or semicolon separated list of denials that are to be included.\nIn this mode, _only_ plugins in the list are bypassed. All other plugins\ncan immediately reject connections.\n\n### excluded plugins\n\nA comma or semicolon separated list of denials that are to be excluded.\nExcluded plugins that are not bypassed and can still immediately reject\nconnections.\n\n<!-- File: docs/plugins/max_unrecognized_commands.md (Tokens: ~56 | Category: anthropic-skill) -->\n# max_unrecognized_commands\n\nThe functionality of this plugin was folded into\n[haraka-plugin-limit](https://github.com/haraka/haraka-plugin-limit).\nLoading `max_unrecognized_commands` in `config/plugins` is auto-redirected\nto `limit`.\n\n<!-- File: docs/plugins/prevent_credential_leaks.md (Tokens: ~159 | Category: anthropic-skill) -->\n# prevent_credential_leaks\n\nThis plugin prevents an authenticated user (via SMTP AUTH) from sending\ntheir username and password out in a message (e.g. like replying to a\nphish).\n\nIf their username and password are detected inside the message body, then\nthe message is rejected with the message:\n\n```\nCredential leak detected: never give out your username/password to anyone!\n```\n\nNote that if the username is qualified e.g. user@domain.com - then the\nplugin will search for both `user` and `user@domain.com` for maximum\neffectiveness.\n\n## Configuration\n\nNo configuration is required. Simply add the plugin to your `config/plugins`\nfile. It should be added before any other plugins that run on hook_data_post\nfor maximum efficiency.\n\n<!-- File: docs/plugins/process_title.md (Tokens: ~760 | Category: anthropic-skill) -->\n# process_title\n\nThis plugin causes the process title seen by the UNIX 'ps' command to\nbe modified from this:\n\n```\nnode haraka.js -c /etc/haraka\n```\n\nto this:\n\n```\nHaraka (master) cn=11148 cc=1082 cps=21/25.24/79 rcpts=144950/1.84 rps=518/328.18/586 msgs=78815/7.07 mps=302/178.44/329 out=0/0/0 respawn=0\n \\_ Haraka (worker) cn=1646 cc=140 cps=5/3.73/17 rcpts=20310/1.86 rps=75/46.04/102 msgs=10938/6.65 mps=42/24.8/56 out=0/0/0\n \\_ Haraka (worker) cn=1563 cc=168 cps=3/3.54/18 rcpts=19844/1.87 rps=78/45/96 msgs=10627/6.8 mps=49/24.1/53 out=0/0/0\n \\_ Haraka (worker) cn=1852 cc=172 cps=3/4.2/16 rcpts=26278/2.03 rps=93/59.56/114 msgs=12938/6.99 mps=40/29.33/65 out=0/0/0\n \\_ Haraka (worker) cn=1704 cc=187 cps=5/3.86/14 rcpts=23688/1.84 rps=93/53.7/125 msgs=12886/7.56 mps=64/29.21/66 out=0/0/0\n \\_ Haraka (worker) cn=2296 cc=218 cps=2/5.2/20 rcpts=29300/1.78 rps=117/66.4/125 msgs=16489/7.18 mps=40/37.37/66 out=0/0/0\n \\_ Haraka (worker) cn=2091 cc=195 cps=4/4.74/16 rcpts=25646/1.71 rps=84/58.12/117 msgs=14982/7.16 mps=52/33.95/66 out=0/0/0\n```\n\nwhere:\n\n- cn = Total number of connections\n- cc = Total number of concurrent connections\n- cps = Number of connections in the last second / average / maximum\n- rcpts = Total number of recipients / Average number of recipients per message\n- rps = Number of recipients in the last second / average / maximum\n- msgs = Total number of messages / Average number messages per connection\n- mps = Number of messages in the last second / average / maximum\n- out = Mails being processed / Mails waiting to be processed / Mails in temp fail state\n- respawn = Number of worker processes respawned (only under cluster)\n\nIf 'cluster' is used then the master process will show the total\nacross all workers, with the exception of outbound stats.\n\nAll of the counts shown are since the process started, so if a\nworker has been re-started then the counts may not add up.\n\nNote: this plugin should be added at the top of `config/plugins` so\nthat its `connect_init`, `rcpt`, `data`, and `disconnect` hooks run\nbefore any plugin that might short-circuit those hooks.\n\n<!-- File: docs/plugins/queue/deliver.md (Tokens: ~22 | Category: anthropic-skill) -->\n# queue/deliver\n\nThis plugin is now redundant. Outbound delivery is now built into Haraka.\n\n<!-- File: docs/plugins/queue/discard.md (Tokens: ~164 | Category: anthropic-skill) -->\n# discard\n\nThis plugin will discard a message by pretending that the message was queued.\n\nIt is designed to be used by other plugins which request the message be\ndiscard by setting a connection or transaction note that this plugin\nchecks.\n\nIt uses the 'queue' hook, so it runs after all the plugins that hook on `data_post`.\n\nIf you use the 'quarantine' plug-in then this plugin should run _after_ it.\n\nUSE THIS PLUGIN WITH CARE!\n\n# Enable\n\nEnable by adding a `queue/discard` entry in `config/plugins` **before** your\nother queue plugins that perform actual deliveries.\n\n# Usage\n\nSet\n\n```javascript\nconnection.notes.discard = [1 | true]\n```\n\nor\n\n```javascript\nconnection.transaction.notes.discard = [1 | true]\n```\n\n<!-- File: docs/plugins/queue/lmtp.md (Tokens: ~151 | Category: anthropic-skill) -->\n# queue/lmtp\n\nThis plugin delivers inbound mail via LMTP.\n\n## Configuration\n\nLMTP is enabled by adding `queue/lmtp` to config/plugins. LMTP delivery is configured in `config/lmtp.ini` . By default, all inbound messages are forwarded to the host specified in the `[main]` section. Domain specific routes can be specified by creating additional sections with the same host/port or path options.\n\n### lmtp.ini\n\n```ini\n; defaults\nhost=localhost\nport=24\n\n[example1.com]\n; Goes elsewhere\nhost=10.1.1.1\nport=2400\n\n[example2.com]\n; Using unix domain sockets\npath = /tmp/blah_com_socket\n```\n\n<!-- File: docs/plugins/queue/qmail-queue.md (Tokens: ~111 | Category: anthropic-skill) -->\n# queue/qmail-queue\n\nThis plugin delivers the mail to the `qmail-queue` program, which can be used\nfor both inbound and outbound delivery.\n\n## Configuration\n\n- qmail-queue.path\n\n  The path to the `qmail-queue` binary. Default: `/var/qmail/bin/qmail-queue`\n\n- qmail-queue.ini\n  - enable_outbound=true\n\n    Deliver outbound email to qmail. Set to false to use Haraka's\n    separate Outbound mail routing (MX based delivery)).\n\n<!-- File: docs/plugins/queue/quarantine.md (Tokens: ~638 | Category: anthropic-skill) -->\n# quarantine\n\nThis plugin will save a message (in message/rfc822 format) to a specified\ndirectory, which will be created automatically if it does not already exist,\na dated sub-folder is also added to the end of the path specified in YYYYMMDD\nformat.\n\nIt is designed to be used by other plugins which request the message be\nquarantined by setting a connection or transaction note that this plugin\nchecks.\n\nNOTE: this plugin simply saves a copy of the message. It does not reject or\ndiscard the message and relies on another plugin to perform this function.\n\nIt uses the 'queue' hook, so that it runs after all the 'data_post' plugins\nand should be listed in 'config/plugins' to run before your queue hooks that\nperform actual deliveries.\n\nTo ensure that only completely written files are present in the quarantine,\nthe files are written to a temporary directory first and then hardlinked to\nthe final destination before the temporary file is deleted.\n\nThe temporary directory is 'quarantine_path/tmp' which defaults to:\n/var/spool/haraka/quarantine/tmp.\n\nUpon start-up, any files present in the temporary directory are deleted\nsyncronously prior to any messages being accepted.\n\n## Configuration\n\nThis plugin looks for 'quarantine.ini' in the config directory.\n\n- quarantine_path (default: /var/spool/haraka/quarantine)\n\n  The default base path to save the quarantine files to. It will be created\n  if it does not already exist.\n\n## Usage\n\nIf you wish to keep a copy of the message in your plugin, simply either:\n\n```javascript\nconnection.notes.quarantine = [1 | true | 'sub/directory/path']\n```\n\nor\n\n```javascript\nconnection.transaction.notes.quarantine = [1 | true | 'sub/directory/path']\n```\n\ne.g.\n\n```javascript\nconnection.notes.quarantine = 1\n```\n\nwould save the message to '/var/spool/quarantine/haraka/YYYYMMDD/UUID' where\nYYYMMDD and UUID are expanded to current date and transaction UUID.\n\nand\n\n```javascript\nconnection.notes.quarantine = 'corpus'\n```\n\nwould save the message to '/var/spool/quarantine/haraka/corpus/YYYYMMDD/UUID'.\n\nNote: you can specify 'corpus/foo' or 'corpus/foo/bar' and the directories will\nbe automatically created. Do not add any leading or trailing slashes.\n\nBy default - after the message is quarantined, the plugin will tell Haraka to\ncontinue to the next plugin. You can specify a different action like DENY or\nOK and supply an optional message using the following notes:\n\n```javascript\nconnection.notes.quarantine_action = [OK, 'Message quarantined']\nconnection.transaction.notes.quarantine_action = [DENY, 'Message rejected']\n```\n\nIf you don't want to supply a specific message back to the client you can\nalso just specify a return code:\n\n```javascript\nconnection.notes.quarantine_action = OK\nconnection.transaction.notes.quarantine_action = DENY\n```\n\n<!-- File: docs/plugins/queue/smtp_bridge.md (Tokens: ~225 | Category: anthropic-skill) -->\n# queue/smtp_bridge\n\nThis plugin delivers to another SMTP server, bridging the authentication\ndetails and post data from the initial connection.\n\nThis plugin is meant to be used with the plugin `auth/auth_bridge`.\n\nIt is different than `queue/smtp_proxy` or `queue/smpt_forward` because\nit doesn't use the AUTH details from a configuration file. This plugins\nsimply post the data from the original connection to the remote SMTP server\nusing the original AUTH details.\n\n## Configuration\n\nConfiguration is stored in `config/smtp_bridge.ini` and uses the INI\nstyle formatting.\n\nThe configuration of this plugin is simple:\n\n    host=localhost\n    #port=\n    #auth_type=\n    #priority=10\n\n- host: the host where you will be authenticating and posting,\n  for example `smtp.host.tld`. This is the only setting required.\n\nIf needed you can also set\n\n- port: default to empty and Haraka will use 25.\n- auth_type: default to empty and Haraka will try to pick an appropriate method.\n- priority: default to 10.\n\n<!-- File: docs/plugins/queue/smtp_forward.md (Tokens: ~1101 | Category: anthropic-skill) -->\n# queue/smtp_forward\n\n==================\n\nThis plugin delivers to another mail server. This is a common setup when you want to have a mail server with a solid pedigree of outbound delivery to other hosts, and inbound delivery to users.\n\nIn comparison to `queue/smtp_proxy`, this plugin waits until queue time to attempt the ongoing connection. This can be a benefit in reducing connections to your inbound mail server when you have content filtering (such as spamassassin) enabled. A possible downside is that it also delays recipient validation that the ongoing mail server may provide until queue time.\n\n## Configuration\n\n---\n\nConfiguration is stored in smtp_forward.ini in the following keys:\n\n- enable_outbound=[true]\n\n  SMTP forward outbound messages (set to false to enable Haraka's separate Outbound mail routing (MX based delivery)).\n\n- host=HOST\n\n  The host to connect to.\n\n- port=PORT\n\n  The port to connect to. Default: 25\n\n- connect_timeout=SECONDS\n\n  The maximum amount of time to wait when creating a new connection to the host. Default: 30 seconds.\n\n- timeout=SECONDS\n\n  The amount of seconds to let a backend connection live idle in the connection pool. This should always be less than the global plugin timeout, which should in turn be less than the connection timeout.\n\n- max_connections=NUMBER\n\n  Maximum number of connections at any given time. Default: 1000\n\n- enable_tls=[true]\n\n  Enable opportunistic TLS with the forward host via `STARTTLS` (if the host advertises it). This plugin does not work with implicit SMTP over TLS.\n\n- auth_type=[plain\\|login]\n\n  Enable PLAIN or LOGIN SMTP AUTH. This is required to enable AUTH.\n\n- auth_user=USERNAME\n\n  SMTP AUTH username to use.\n\n- auth_pass=PASSWORD\n\n  SMTP AUTH password to use.\n\n- queue\n\n  Which queue plugin to use. Default: undefined. The default bahavior is to use smtp_forward for inbound connections and outbound for relaying connections. This option is used for complex mail routes.\n\n- check_sender=false\n\n  Requires that sender domains defined in smtp_forward.ini (see Per-Domain below) have relaying privileges. This is a form of spoof prevention and assumes that any mail clients have relaying or AUTH privileges. This is usually the case.\n\n- check_recipient=false\n\n  By default, Haraka accepts no emails until a recipient plugin has been configured to accept mails for a domain. The simplest common case is the in_host_list plugin with a list of domains in config/host_list. An alternative is to set `check_recipient=true` and list each domain in a definition block in smtp_forward.ini (see Per-Domain Configuration). An example for two domains:\n\n  [example.com]\n  [example.net]\n\n- [tls]\n\nClient STARTTLS options are assembled by merging:\n\n1. `tls.ini` `[main]` — the global Haraka TLS config\n2. `smtp_forward.ini` `[tls]` — overrides. Anything set here wins.\n\nExample `smtp_forward.ini` `[tls]` section:\n\n    [tls]\n    rejectUnauthorized=true\n    minVersion=TLSv1.2\n    no_tls_hosts[]=10.0.0.5\n\nPer-domain `enable_tls=false` still disables STARTTLS for that backend. Per-domain TLS cipher/cert overrides are not currently supported.\n\nChanges to `tls.ini` require a Haraka restart to apply to the forward path; changes to `smtp_forward.ini` are picked up by the existing reload hook.\n\n# Per-Domain Configuration\n\nMore specific forward routes for domains can be defined. The domain is chosen based on the value of the `domain_selector` config variable.\n\nWhen `domain_selector` is set to `rcpt_to` (the default), more specific routes are only honored for SMTP connections with a single recipient or SMTP connections where every recipient host is identical.\n\nWhen `domain_selector` is set to `mail_from`, it first searches for configuration using the complete email address, falls back to the domain if not found, then to main configuration.\n\nenable_outbound can be set or unset on a per-domain level to enable or disable forwarding for specific domains.\n\n    # default SMTP host\n    host=1.2.3.4\n    # auth_type=plain\n    # auth_user=user\n    # auth_user=pass\n\n    [example1.com]\n    host=1.2.3.5\n    # auth_type=plain\n    # auth_user=user\n    # auth_pass=pass\n\n    [email@example1.com]\n    host=1.2.3.5\n    # auth_type=plain\n    # auth_user=user\n    # auth_pass=pass\n\n    [example2.com]\n    host=1.2.3.5\n\n    [example3.com]\n    host=1.2.3.6\n\n    [example4.com]\n    enable\\_outbound=false\n\n# Split host forward routing\n\nWhen an incoming email transaction has multiple recipients with different forward routes, recipients to subsequent forward routes are deferred. Example: an incoming email transaction has recipients user@example1.com, user@example2.com, and user@example3.com. The first two recipients will be accepted (they share the same forward destination) and the latter will be deferred. It will arrive in a future delivery attempt by the remote.\n\n<!-- File: docs/plugins/queue/smtp_proxy.md (Tokens: ~471 | Category: anthropic-skill) -->\n# queue/smtp_proxy\n\n================\n\nThis plugin delivers to another mail server. This is a common setup when you want to have a mail server with a solid pedigree of outbound delivery to other hosts, and inbound delivery to users.\n\nIn comparison to `queue/smtp_forward`, this plugin makes a connection at MAIL FROM time to the ongoing SMTP server. This can be a benefit in that you get any SMTP-time filtering that the ongoing server provides, in particular one important facility to some setups is recipient filtering.\n\nBe aware that other than connect and HELO-time filtering, you will have as many connections to your ongoing SMTP server as you have to Haraka.\n\n## Configuration\n\n---\n\nConfiguration is stored in smtp_proxy.ini in the following keys:\n\n- enable_outbound=[true]\n\n  SMTP proxy outbound messages (set to false to enable Haraka's\n  separate Outbound mail routing (MX based delivery)).\n\n- host=HOST\n\n  The host to connect to.\n\n- port=PORT\n\n  The port to connect to.\n\n- connect_timeout=SECONDS\n\n  The maximum amount of time to wait when creating a new connection\n  to the host. Default if unspecified is 30 seconds.\n\n- timeout=SECONDS\n\n  The amount of seconds to let a backend connection live idle in the\n  proxy pool. This should always be less than the global plugin timeout,\n  which should in turn be less than the connection timeout.\n\n- max_connections=NUMBER\n\n  Maximum number of connections to create at any given time.\n\n- enable_tls=[true|yes|1]\n\n  Enable opportunistic TLS with the forward host via `STARTTLS` (if the host advertises it).\n\n- auth_type=[plain|login]\n\n  Enable PLAIN or LOGIN SMTP AUTH. This is required to enable AUTH.\n\n- auth_user=USERNAME\n\n  SMTP AUTH username to use.\n\n- auth_pass=PASSWORD\n\n  SMTP AUTH password to use.\n\n- [tls]\n\nClient STARTTLS options are assembled by merging:\n\n1. `tls.ini` `[main]` — the global Haraka TLS config.\n2. `smtp_proxy.ini` `[tls]` — overrides. Anything set here wins.\n\nChanges to `tls.ini` require a Haraka restart to apply to the proxy path; changes to `smtp_proxy.ini` are picked up by the existing reload hook.\n\n<!-- File: docs/plugins/queue/test.md (Tokens: ~89 | Category: anthropic-skill) -->\n# queue/test\n\nThis plugin saves incoming E-Mail to your temporary directory, as `mail_{message_id}.eml`, where message_id is a UUID.\n\nThis plugin can be useful to quickly test if you're able to receive incoming E-Mail and just dump them to disk.\n\nThe temporary directory is determined using Node's [`os.tmpdir()`](https://nodejs.org/api/os.html#ostmpdir), which respects standard platform configurations.\n\n<!-- File: docs/plugins/rcpt_to.in_host_list.md (Tokens: ~303 | Category: anthropic-skill) -->\n# rcpt_to.in_host_list\n\nThis plugin is the mainstay of an inbound Haraka server. It should list the\ndomains that are local to the host. Mails that have RCPT TO not matching\na host in the given list will be passed onto other rcpt hooks. If no rcpt\nhook accepts the connection, it will be rejected.\n\n## Configuration\n\n- host_list\n\n  Specifies the list of hosts that are local to this server.\n\n- host_list_regex\n\n  Specifies the list of regexes that are local to this server. Note\n  all these regexes are anchored with ^regex$. One can choose not to\n  anchor with .\\*. There is the potential for bad regexes to be\n  too permissive if we don't anchor.\n\n- host_list.anti_spoof\n\n  When enabled, this will cause Haraka to reject any MAIL FROM where\n  the host appears within the host list but the connected host is not\n  a relay, e.g. connection.relaying is not set either by SMTP AUTH or\n  another plugin like 'relay'.\n\n## Relaying\n\nThis plugin checks to see if the MAIL FROM domain is local. When\nconnection.relaying is detected (haraka -h relay) and the MAIL FROM domain is\nlocal, this plugin will vouch for any RCPT. This limits relaying users to\nsending from local domains, which is much safer than letting relay clients\nsend from any domain.\n\n<!-- File: docs/plugins/rcpt_to.max_count.md (Tokens: ~35 | Category: anthropic-skill) -->\n# rcpt_to.max_count\n\nThe functionality of this plugin was integrated in to [haraka-plugin-limit](https://github.com/haraka/haraka-plugin-limit).\n\n<!-- File: docs/plugins/record_envelope_addresses.md (Tokens: ~155 | Category: anthropic-skill) -->\n# record_envelope_addresses\n\nThis plugin adds two new header lines.\n\n- X-Envelope-To: the envelope RCPT TO address\n- X-Envelope-From: the envelope MAIL FROM address\n\nIt is useful if you need to know the exact addresses used to send an email, e.g. when\nthe email was sent to you as BCC or if it is a newsletter. In both cases the recipient\naddress is normally not recorded in the headers.\n\n## Caveats\n\nIf you enable this plugin you may introduce a possible information leak, i.e. disclosure\nof BCC recipients. So you never want to use this on an outgoing mail server and maybe also\nnot if this server is used as a relay.\n\n## Configuration\n\nThis plugin has no configuration.\n\n<!-- File: docs/plugins/relay.md (Tokens: ~28 | Category: anthropic-skill) -->\n# relay\n\nRepackaged as [haraka-plugin-relay](https://github.com/haraka/haraka-plugin-relay).\n\n<!-- File: docs/plugins/reseed_rng.md (Tokens: ~131 | Category: anthropic-skill) -->\n# reseed_rng\n\nReseeds `Math.random()` in each cluster worker at start-up using\n`crypto.randomBytes(256)`. Without this, workers forked at nearly the\nsame time can end up with correlated PRNG state, which can produce\nUUID collisions and other \"this should be impossible\" bugs.\n\nThe plugin relies on [seedrandom](https://www.npmjs.com/package/seedrandom)\nbeing loaded so that `Math.seedrandom()` is available.\n\nAnyone running with `nodes=...` in `smtp.ini` (i.e. cluster mode) should\nconsider enabling this plugin.\n\n## Configuration\n\nNo configuration.\n\n<!-- File: docs/plugins/status.md (Tokens: ~443 | Category: anthropic-skill) -->\n# Status\n\nThis plugin allows to get internal status of queues and pools with SMTP commands sent from localhost.\n\n## Communication\n\n- **Request** &rarr; `STATUS <CMD> [param1] [param2]....`\n- **Response** &larr; _&lt;SMTP code 211 or 500>&lt;space>&lt;json encoded response>\\r\\n_\n\n### Example\n\n```\n< 220 example.com ESMTP Haraka ready\n> STATUS QUEUE INSPECT\n< 211 {\"delivery_queue\":[],\"temp_fail_queue\":[]}\n```\n\n## Available commands list\n\n- `STATUS POOL LIST` - map of active outbound connection pools, keyed by `host:port`\n- `STATUS QUEUE STATS` - queue statistics in format `\"<in_progress>/<delivery_queue length>/<temp_fail_queue length>\"`\n- `STATUS QUEUE LIST` - list of queue files on disk with _uuid, domain, mail_from, rcpt_to_ attributes\n- `STATUS QUEUE INSPECT` - returns merged content of `outbound.delivery_queue` and `outbound.temp_fail_queue` across all workers\n- `STATUS QUEUE DISCARD file` - stop delivering email file\n- `STATUS QUEUE PUSH file` - try to re-deliver email immediately\n\n## Notes\n\n### Live data only\n\n`POOL LIST`, `QUEUE STATS`, and `QUEUE INSPECT` reflect live in-memory state. They show only messages currently being processed or waiting in the retry queue. `QUEUE LIST` reads queue files from disk and may show messages that have already been delivered if they haven't been cleaned up yet.\n\n### Cluster mode\n\nIn cluster mode, `POOL LIST`, `QUEUE STATS`, and `QUEUE INSPECT` aggregate results from all worker processes into a single response:\n\n- `POOL LIST` — pool maps from all workers are merged into one object\n- `QUEUE STATS` — counters from all workers are summed into a single `\"N/N/N\"` string\n- `QUEUE INSPECT` — `delivery_queue` and `temp_fail_queue` arrays from all workers are concatenated\n\n`QUEUE LIST` always runs on the master process since it reads shared queue files from disk.\n\n<!-- ... +4 more modular skills available via /api/skills/haraka/Haraka?path={path} -->\n</yakaai_skills>"}