Skip to main content
skill CX Codex

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.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add glitternetwork/pinme --skill pinme-email -a claude-code
Open on GitHub

What 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_email call without rereading the API docs

Example prompts for the agent

  • Add a verification-code email to backend/src/worker.ts using send_email
  • Write a callPinmeAPI helper 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-code

2Manually: put the files in place

~/.claude/skills/pinme-email/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/pinme-email/

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

Similar skills

All skills for Backend and APIs →

From the same set5

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor