Bitwarden Mailcatcher Tools
Skill 42 of 83
Retrieves a link from Mailcatcher.
3 minutes · 732 words · 5 sections
Install
npx skills add bitwarden/ai-plugins --skill reading-mailcatcher-apinpx skills add bitwarden/ai-plugins/plugin marketplace add bitwarden/ai-pluginsThe first command installs just this skill, by the name in its SKILL.md; the second installs the whole repository.
For all programmatic uses (test runs, ad-hoc fetches, debugging), call the co-located script directly.
${CLAUDE_PLUGIN_ROOT}/skills/reading-mailcatcher-api/scripts/read_mailcatcher.py --recipient <email> [--pattern <subject-keyword>] [--link-filter <regex>]For example, to pull a verification link:
${CLAUDE_PLUGIN_ROOT}/skills/reading-mailcatcher-api/scripts/read_mailcatcher.py --recipient testuser-s1@example.com --pattern "Verify"--pattern is optional; omit it to select the most recent message for the recipient--link-filter is optional; it defaults to verify|confirm|signup|token|trial|login|finish-signup — the starting filter that the exit-1 rows below may tell you to loosen or fixread_mailcatcher.py table below (the get_admin_email.py helper has its own codes, noted where it is introduced)| Exit | Meaning | Correct response |
|---|---|---|
| 0 | matching URL printed on stdout | use it |
| 1 | NO_MATCH: no email for recipient … — no matching message arrived (the script retried once after a 3s sleep) | if the email may not have arrived yet (a slow async send), re-invoke the script once more — a single fresh call, not a loop; the script’s own 3-second retry supplies the delay. Otherwise the filters are too narrow: re-check --recipient, then widen or drop --pattern. |
| 1 | matched a message, but no URL matched --link-filter (no retry; stderr: matched but contained no URL filtered by …) | the link is there but the filter missed it; loosen or fix --link-filter |
| 1 | matched URLs, but none is on the local dev allowlist (no retry; stderr: none of the N filter-matched URL(s) is a local dev host) | stderr names the rejected host(s). This is an environment change outside the skill: ask the user to add the host to MAILCATCHER_ALLOWED_HOSTS in their shell (see Prerequisites), then retry. Do not prefix the assignment onto the script invocation — that falls outside the skill’s Bash grant and re-prompts every call. |
| 2 | usage error (bad or missing arguments) | fix the invocation |
| 3 | Mailcatcher unreachable or returned invalid JSON | Mailcatcher is not running or unreachable. Starting it falls outside this skill’s Bash grant: ask the user to start it (see Prerequisites), then retry. |
The script already retries once after a 3-second sleep on the first miss; callers don’t need their own retry loop. See ${CLAUDE_PLUGIN_ROOT}/skills/reading-mailcatcher-api/references/manual-api-walkthrough.md for the underlying Mailcatcher API the script wraps — consult it when modifying the script or debugging unexpected output.
A second granted helper, ${CLAUDE_PLUGIN_ROOT}/skills/reading-mailcatcher-api/scripts/get_admin_email.py, prints the Bitwarden dev admin address (read only from adminSettings.admins in your bitwarden/server checkout’s dev/secrets.json). It defaults to server/dev/secrets.json relative to the current directory, so run it from the workspace root that contains the server checkout, or pass --secrets-file <path>, which must resolve to a dev/secrets.json file (any other path exits 2 without being read). Use it to supply --recipient for the Admin Portal magic-link flow. Its exit codes differ from the table above: 2 = usage error, including a --secrets-file that is not a dev/secrets.json path; 3 = the secrets file is missing, unreadable, invalid JSON, or not a JSON object, or its adminSettings.admins key is missing, empty, or not a string. See ${CLAUDE_PLUGIN_ROOT}/skills/reading-mailcatcher-api/references/email-patterns.md for the exact invocation, the --secrets-file override, and the --all flag.
Mailcatcher must be running before invoking the script (the mail service in bitwarden/server‘s dev/docker-compose.yml; start it with docker compose --profile mail up -d mail from server/dev/). If it isn’t, the script exits 3 with an ERROR: Mailcatcher unreachable message on stderr. A WARNING: plain body empty ... using HTML body for URL extraction line on stderr is non-fatal: the script falls back to the HTML body and still succeeds.
The script always reads Mailcatcher at http://localhost:1080, the fixed endpoint across Bitwarden dev environments; there is no URL override. Extracted URLs are restricted to a local dev host allowlist (localhost, 127.0.0.1, ::1, bitwarden.test). If your environment’s emails link to another local hostname, it must be added to the comma-separated MAILCATCHER_ALLOWED_HOSTS env var, which extends (never replaces) that allowlist. This is an environment change the user makes in their shell, not something to prefix onto the script invocation: an assignment prepended to the command falls outside the skill’s script:* Bash grant, so it re-prompts on every call and does not persist.
See ${CLAUDE_PLUGIN_ROOT}/skills/reading-mailcatcher-api/references/email-patterns.md for subject lines, link formats, and extraction commands for each email type: account verification, Admin Portal magic link, trial activation, organization invite, emergency access, and the welcome email.
fetch('http://localhost:1080/...') from Playwright’s browser context; always use the co-located script from the agent shellRetrieves a link from Mailcatcher. Use when a local testing or debugging flow needs a link from an email, such as account verification links, magic-link logins, trial activations, organization invites, emergency access, and other email-driven flows. Preferred over the Mailcatcher browser UI in automation, where Playwright's CORS blocks a direct fetch. Do NOT use it to configure SMTP, start Mailcatcher, or debug delivery.
The verbatim description from this skill’s front matter — the string an agent matches on to decide whether to load it.
Read(/${CLAUDE_PLUGIN_ROOT}/skills/reading-mailcatcher-api/references/**)Bash(${CLAUDE_PLUGIN_ROOT}/skills/reading-mailcatcher-api/scripts/read_mailcatcher.py:*)Bash(${CLAUDE_PLUGIN_ROOT}/skills/reading-mailcatcher-api/scripts/get_admin_email.py:*)plugins/bitwarden-mailcatcher-tools/skills/reading-mailcatcher-api/SKILL.mdmain, last pushed 6 November 2026.SKILL.md, not by matching a directory convention. 20 distinct layouts observed: plugins/bitwarden-abacus-tools/skills/*/SKILL.md, plugins/bitwarden-adr-tools/skills/*/SKILL.md, plugins/bitwarden-atlassian-tools/skills/*/SKILL.md, plugins/bitwarden-code-review/skills/*/SKILL.md, plugins/bitwarden-delivery-tools/skills/*/SKILL.md, plugins/bitwarden-design-tools/skills/*/SKILL.md, plugins/bitwarden-designer/skills/*/SKILL.md, plugins/bitwarden-devops-engineer/skills/*/SKILL.md, plugins/bitwarden-doc-parity/skills/*/SKILL.md, plugins/bitwarden-mailcatcher-tools/skills/*/SKILL.md, plugins/bitwarden-product-analyst/skills/*/SKILL.md, plugins/bitwarden-retrospective-tools/skills/*/SKILL.md, plugins/bitwarden-security-engineer/skills/*/SKILL.md, plugins/bitwarden-shepherd/skills/*/SKILL.md, plugins/bitwarden-stripe-tools/skills/*/SKILL.md, plugins/bitwarden-tech-lead/skills/*/SKILL.md, plugins/bitwarden-testing-tools/skills/*/SKILL.md, plugins/bitwarden-testmo-tools/skills/*/SKILL.md, plugins/claude-config-validator/skills/*/SKILL.md, plugins/claude-retrospective/skills/*/SKILL.md.h1 and no skipped levels:.claude-plugin/marketplace.json by Bitwarden, declaring 23 plugins. It is read for editorial metadata only — never as the skill index, which is always the repository tree./bitwarden/ai-plugins.md, and each skill at its own .md URL.8 files · 64 KB
Everything this skill ships beside its prose. All of it is set here, as subchapters of skill 42.
Documentation the agent loads on demand, rather than up front.
Executable code the skill can run.
Everything else published alongside the skill.