Skip to content
Agentmart
Submit a tool

email

Transactional email provider integration, templates, and delivery rules.

Skill by Mihir-Rabari

IndexedMIT
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/mihir-rabari-email-8fbd63

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Transactional email provider integration, templates, and delivery rules.

Use cases: productivity

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/email

What's inside

Skill name
email
Description
Transactional email provider integration, templates, and delivery rules.
License
MIT
Instructions
57 lines in SKILL.md · view on GitHub

Agent Skills spec issues

  • files: referenced but missing: templates/index.ts

Files (1)

  • SKILL.md

SKILL.md (first lines)

# Email Skill

## 1. When to Use
Use this skill when sending any email: verification codes, password resets, invitations, notices, or integrating an email provider (Resend).

## 2. How It Is Built
All of it lives in `packages/shared/src/email/`. Nothing else in the codebase talks to a provider.

| Piece | File | Job |
|---|---|---|
| `EmailService` | `email-service.ts` | The one entry point. Validates the recipient, derives a plain-text part, picks a transport. |
| Transports | `transports.ts` | `ResendTransport` (real), `ConsoleTransport` (prints, for development), `MemoryTransport` (records, for tests). |
| Templates | `templates/` | One shared layout plus one function per email. Each returns `{ subject, html, text }`. |
| `Mailer` | `apps/api/src/lib/mailer.ts` | App layer: knows the product name and the web origin for links, and how a route should treat a failed send. |

The API creates the service in `plugins/services.ts` and exposes it as `fastify.email`. Routes use `createMailer(fastify)`, not `fastify.email` directly.

## 3. Configuration
- `RESEND_API_KEY`, `RESEND_FROM` in `.env`. `WEB_URL` is the origin used for every link in an email.
- **No key, development or test**: the console transport prints the message (including any code or link) to the API log. Signup, reset and invite flows work with no provider account.
- **No key, anywhere else**: sending throws `EmailNotConfiguredError`. Production fails loudly instead of silently dropping mail, and `POST /auth/otp/request` answers `503 EMAIL_NOT_CONFIGURED`.
- `email.isConfigured()` is true only for a real provider. The console transport reports `false`, which is how the API knows it may return a `devCode` to the signup form. A `devCode` is never returned in production.

## 4. The Emails
| Template | Sent when | Failure handling |
|---|---|---|
| `verificationCodeEmail` | `POST /auth/otp/request` | Awaited. In production a failure is a 5xx; in development the code is returned as `devCode` instead. |
| `welcomeEmail` | A workspace is created | Background. The account exists either way. |
| `passwordResetEmail` | `POST /auth/password/forgot` | Background, so response time does not reveal whether the address has an account. |
| `passwordChangedEmail` | Password reset or change | Background. A security notice, so a takeover is noticed. |
| `inviteEmail` | `POST /iam/users`, `POST /iam/users/:id/invite` | Awaited; the response carries `inviteSent`. The account exists regardless, and the administrator can resend. |

## 5. Invariants
- **Escape everything user-controlled.** Templates build HTML from workspace names and display names. Interpolate through `escapeHtml`; never concatenate raw input into markup.
- **Links are absolute http(s) only.** `safeUrl` rejects `javascript:` and relative URLs; `renderLayout` calls it for every button.
- **No header injection.** Recipients are validated with a strict email schema and subjects may not contain line breaks. `EmailService` enforces both.
- **Tokens in links are single-use and expiring.** They come from `ActionTokenService` (`@packages/auth`), which stores only a SHA-256 of each token. A reset token lives an hour, an invite three days.
- **Never put a password in an email.** Invitations carry a link to *choose* one.
- **Never reveal whether an address has an account.** `POST /auth/password/forgot` answers identically either way and sends nothing for unknown, suspended or disabled accounts.
- **Do not log addresses or message bodies** outside the console transport, which exists for development.
- **A `text` part is always sent**, derived from the HTML. Do not write a template whose meaning lives only in markup.

## 6. Adding an Email
1. Add a function to `templates/index.ts` that calls `renderLayout` and returns `finish(subject, html)`.
2. Decide how a failure should behave (table above) and pick `mailer.send` or `mailer.sendInBackground` accordingly.
3. Test it with `MemoryTransport` (see Testing).

## 7. Mandatory Testing Expectations
- Never call a live provider in automated tests. Use `MemoryTransport` through `buildApp({}, { email })`; the route tests in `apps/api/src/tenancy.test.ts` show the pattern.
- Each template: subject, html and text all carry the essential content (code, link).
- Escaping: a hostile name (`<script>`, `"><img onerror>`) must come out inert.
- Recipient validation, including a CRLF injection attempt.
- Behaviour with no provider: refuses in production, prints in development.
- Delivery failure: the invite still creates the account and reports `inviteSent: false`.
- Enumeration: forgot-password sends nothing and answers identically for an unknown address.

From Mihir-Rabari/create-odoo-app, MIT.

More from Mihir-Rabari/create-odoo-app

Signals

Stars
0
Forks
1
Open issues
3
Last commit (this folder)
Oct 2, 2026 (26 days ago)
Last release
Sep 4, 2026 (1 month ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

Same skill elsewhere

Copies of this SKILL.md published by others; search shows only one of them.

More from Mihir-Rabari

Cite this page

email skill. Agent Mart. https://agentmart.dev/skills/mihir-rabari-email-8fbd63 (last updated 2026-10-24)