pinme-email — Claude Code skill: send email from a PinMe Worker
Tells the agent how to wire PinMe's email endpoint into a TypeScript Worker, so the request code comes out right the first time.
npx skills add glitternetwork/pinme --skill pinme-email -a claude-codeWhat pinme-email does
The skill covers POST {BASE_URL}/api/v4/send_email: the X-API-Key header, the three body fields (to, subject, html), and the sender address, which PinMe sets to {project_name}@pinme.cloud. It ships TypeScript snippets for a sendEmail helper plus a callPinmeAPI wrapper that reads the platform's code/msg/data response shape.
Credentials are already there once the Worker exists. API_KEY and BASE_URL arrive as environment variables, and BASE_URL falls back to https://pinme.cloud when unset. The skill also maps the failure codes: 401 for a bad or missing key, 400 for a rejected address or empty subject, 500 when the mail service itself errors.
Scope is narrow by design. Only PinMe Workers on TypeScript and API v4 are covered, so the snippets won't transfer to some other provider or endpoint — the auth header and field names are PinMe-specific.
What it helps with
- send a verification code from a Worker
- build an HTML body and deliver it to a recipient
- turn a failed send into a readable error for the client
- write the
send_emailcall without rereading the API docs
Example prompts for the agent
- Add a verification-code email to
backend/src/worker.tsusingsend_email - Write a
callPinmeAPIhelper that surfaces the PinMe error message as text - Make the route return a clear error on 401 and 400
How to install pinme-email
1Skills CLI (Vercel)
npx skills add glitternetwork/pinme --skill pinme-email -a claude-code2Manually: put the files in place
~/.claude/skills/pinme-email/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/pinme-email/
1Skills CLI (Vercel)
npx skills add glitternetwork/pinme --skill pinme-email -a codex2Manually: put the files in place
.agents/skills/pinme-email/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add glitternetwork/pinme --skill pinme-email -a cursor2Manually: put the files in place
.cursor/skills/pinme-email/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install glitternetwork/pinme pinme-email2Manually: put the files in place
.github/skills/pinme-email/SKILL.md1Skills CLI (Vercel)
npx skills add glitternetwork/pinme --skill pinme-email -a gemini-cli2Platform CLI
gemini skills install https://github.com/glitternetwork/pinme --path skills/pinme-emailManually: put the files in place
.devin/skills/pinme-email/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add glitternetwork/pinme --skill pinme-email -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about pinme-email
How do I install pinme-email in Claude Code?
Run npx skills add glitternetwork/pinme --skill pinme-email -a claude-code. The skill lands at ~/.claude/skills/pinme-email/SKILL.md.
What does pinme-email do?
It gives the agent the request shape for PinMe's send_email call, TypeScript examples included, along with how each error status should be handled.
Do I have to configure the API key myself?
No. PinMe injects API_KEY and BASE_URL into the Worker when it is created.
Source text
SKILL.md · MIT
---
name: pinme-email
description: Use this skill when a PinMe project (Worker TypeScript) needs to integrate email sending (send_email). Guides AI to generate correct Worker TS code.
---
# PinMe Worker Email API Integration
Guides how to call PinMe platform's email sending API in a PinMe Worker (TypeScript).
## Environment Variables
The following environment variables are automatically injected when the Worker is created — no manual configuration needed:
```typescript
// backend/src/worker.ts
export interface Env {
DB: D1Database;
API_KEY: string; // Project API Key — used for send_email authentication
BASE_URL?: string; // Optional override for PinMe API base URL, defaults to https://pinme.cloud
}
```
> `API_KEY` is the sole credential for the Worker to call PinMe platform APIs. When `BASE_URL` is not set, it defaults to `https://pinme.cloud`.
---
## Send Email API
**Endpoint:** `POST {BASE_URL}/api/v4/send_email`
**Authentication:** `X-API-Key` header (using `env.API_KEY`)
**Sender:** Automatically set to `{project_name}@pinme.cloud`
### Request Format
```json
{
"to": "[email protected]",
"subject": "Your verification code",
"html": "<p>Your code is <strong>123456</strong></p>"
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `to` | string | Yes | Recipient email address |
| `subject` | string | Yes | Email subject |
| `html` | string | Yes | HTML body |
### Response Format
**Success (200):**
```json
{ "code": 200, "msg": "ok", "data": { "ok": true } }
```
**Errors:**
| HTTP Status | Meaning | data.error Example |
|-------------|---------|-------------------|
| 401 | API Key missing or invalid | `"X-API-Key header is required"` / `"Invalid API key"` |
| 400 | Parameter validation failed | `"Invalid email address"` / `"Subject is required"` |
| 500 | Email service error | `"Failed to send email"` |
### Worker Example Code
```typescript
async function sendEmail(env: Env, to: string, subject: string, html: string): Promise<{ ok: boolean; error?: string }> {
const baseUrl = env.BASE_URL ?? 'https://pinme.cloud';
const resp = await fetch(`${baseUrl}/api/v4/send_email`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': env.API_KEY,
},
body: JSON.stringify({ to, subject, html }),
});
const result = await resp.json() as { code: number; msg: string; data?: { ok?: boolean; error?: string } };
if (resp.status !== 200 || result.code !== 200) {
return { ok: false, error: result.data?.error || result.msg || 'Unknown error' };
}
return { ok: true };
}
// Usage in routes
async function handleSendVerification(request: Request, env: Env): Promise<Response> {
const { email } = await request.json() as { email: string };
const code = Math.random().toString().slice(2, 8);
const result = await sendEmail(env, email, 'Verification Code',
`<p>Your code is <strong>${code}</strong></p>`);
if (!result.ok) {
return json({ error: result.error }, 500);
}
return json({ ok: true });
}
```
---
## Error Handling Pattern
PinMe platform API unified response format:
```typescript
interface PinmeResponse<T = unknown> {
code: number; // 200=success, other=failure
msg: string; // "ok" | "error" | "invalid params"
data?: T; // Business data on success, may contain { error: string } on failure
}
```
### Recommended Unified Error Handler
```typescript
async function callPinmeAPI<T>(url: string, apiKey: string, body: unknown): Promise<{ data?: T; error?: string }> {
let resp: Response;
try {
resp = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-API-Key': apiKey },
body: JSON.stringify(body),
});
} catch {
return { error: 'Network error' };
}
if (!resp.ok) {
try {
const err = await resp.json() as PinmeResponse;
return { error: err.data && typeof err.data === 'object' && 'error' in err.data
? (err.data as { error: string }).error
: err.msg || `HTTP ${resp.status}` };
} catch {
return { error: `HTTP ${resp.status}` };
}
}
const result = await resp.json() as PinmeResponse<T>;
if (result.code !== 200) {
return { error: result.data && typeof result.data === 'object' && 'error' in result.data
? (result.data as { error: string }).error
: result.msg };
}
return { data: result.data as T };
}
```
### Usage Example
```typescript
const baseUrl = env.BASE_URL ?? 'https://pinme.cloud';
// Send email
const emailResult = await callPinmeAPI<{ ok: boolean }>(
`${baseUrl}/api/v4/send_email`, env.API_KEY,
{ to: '[email protected]', subject: 'Hello', html: '<p>Hi</p>' },
);
if (emailResult.error) return json({ error: emailResult.error }, 500);
```
© glitternetwork · MIT · glitternetwork/pinme