$npx -y skills add glitternetwork/pinme --skill pinme-emailUse this skill when a PinMe project (Worker TypeScript) needs to integrate email sending (send_email). Guides AI to generate correct Worker TS code.
| 1 | # PinMe Worker Email API Integration |
| 2 | |
| 3 | Guides how to call PinMe platform's email sending API in a PinMe Worker (TypeScript). |
| 4 | |
| 5 | ## Environment Variables |
| 6 | |
| 7 | The following environment variables are automatically injected when the Worker is created — no manual configuration needed: |
| 8 | |
| 9 | ```typescript |
| 10 | // backend/src/worker.ts |
| 11 | export interface Env { |
| 12 | DB: D1Database; |
| 13 | API_KEY: string; // Project API Key — used for send_email authentication |
| 14 | BASE_URL?: string; // Optional override for PinMe API base URL, defaults to https://pinme.cloud |
| 15 | } |
| 16 | ``` |
| 17 | |
| 18 | > `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`. |
| 19 | |
| 20 | --- |
| 21 | |
| 22 | ## Send Email API |
| 23 | |
| 24 | **Endpoint:** `POST {BASE_URL}/api/v4/send_email` |
| 25 | **Authentication:** `X-API-Key` header (using `env.API_KEY`) |
| 26 | **Sender:** Automatically set to `{project_name}@pinme.cloud` |
| 27 | |
| 28 | ### Request Format |
| 29 | |
| 30 | ```json |
| 31 | { |
| 32 | "to": "user@example.com", |
| 33 | "subject": "Your verification code", |
| 34 | "html": "<p>Your code is <strong>123456</strong></p>" |
| 35 | } |
| 36 | ``` |
| 37 | |
| 38 | | Field | Type | Required | Description | |
| 39 | |-------|------|----------|-------------| |
| 40 | | `to` | string | Yes | Recipient email address | |
| 41 | | `subject` | string | Yes | Email subject | |
| 42 | | `html` | string | Yes | HTML body | |
| 43 | |
| 44 | ### Response Format |
| 45 | |
| 46 | **Success (200):** |
| 47 | ```json |
| 48 | { "code": 200, "msg": "ok", "data": { "ok": true } } |
| 49 | ``` |
| 50 | |
| 51 | **Errors:** |
| 52 | |
| 53 | | HTTP Status | Meaning | data.error Example | |
| 54 | |-------------|---------|-------------------| |
| 55 | | 401 | API Key missing or invalid | `"X-API-Key header is required"` / `"Invalid API key"` | |
| 56 | | 400 | Parameter validation failed | `"Invalid email address"` / `"Subject is required"` | |
| 57 | | 500 | Email service error | `"Failed to send email"` | |
| 58 | |
| 59 | ### Worker Example Code |
| 60 | |
| 61 | ```typescript |
| 62 | async function sendEmail(env: Env, to: string, subject: string, html: string): Promise<{ ok: boolean; error?: string }> { |
| 63 | const baseUrl = env.BASE_URL ?? 'https://pinme.cloud'; |
| 64 | const resp = await fetch(`${baseUrl}/api/v4/send_email`, { |
| 65 | method: 'POST', |
| 66 | headers: { |
| 67 | 'Content-Type': 'application/json', |
| 68 | 'X-API-Key': env.API_KEY, |
| 69 | }, |
| 70 | body: JSON.stringify({ to, subject, html }), |
| 71 | }); |
| 72 | |
| 73 | const result = await resp.json() as { code: number; msg: string; data?: { ok?: boolean; error?: string } }; |
| 74 | |
| 75 | if (resp.status !== 200 || result.code !== 200) { |
| 76 | return { ok: false, error: result.data?.error || result.msg || 'Unknown error' }; |
| 77 | } |
| 78 | return { ok: true }; |
| 79 | } |
| 80 | |
| 81 | // Usage in routes |
| 82 | async function handleSendVerification(request: Request, env: Env): Promise<Response> { |
| 83 | const { email } = await request.json() as { email: string }; |
| 84 | const code = Math.random().toString().slice(2, 8); |
| 85 | |
| 86 | const result = await sendEmail(env, email, 'Verification Code', |
| 87 | `<p>Your code is <strong>${code}</strong></p>`); |
| 88 | |
| 89 | if (!result.ok) { |
| 90 | return json({ error: result.error }, 500); |
| 91 | } |
| 92 | return json({ ok: true }); |
| 93 | } |
| 94 | ``` |
| 95 | |
| 96 | --- |
| 97 | |
| 98 | ## Error Handling Pattern |
| 99 | |
| 100 | PinMe platform API unified response format: |
| 101 | |
| 102 | ```typescript |
| 103 | interface PinmeResponse<T = unknown> { |
| 104 | code: number; // 200=success, other=failure |
| 105 | msg: string; // "ok" | "error" | "invalid params" |
| 106 | data?: T; // Business data on success, may contain { error: string } on failure |
| 107 | } |
| 108 | ``` |
| 109 | |
| 110 | ### Recommended Unified Error Handler |
| 111 | |
| 112 | ```typescript |
| 113 | async function callPinmeAPI<T>(url: string, apiKey: string, body: unknown): Promise<{ data?: T; error?: string }> { |
| 114 | let resp: Response; |
| 115 | try { |
| 116 | resp = await fetch(url, { |
| 117 | method: 'POST', |
| 118 | headers: { 'Content-Type': 'application/json', 'X-API-Key': apiKey }, |
| 119 | body: JSON.stringify(body), |
| 120 | }); |
| 121 | } catch { |
| 122 | return { error: 'Network error' }; |
| 123 | } |
| 124 | |
| 125 | if (!resp.ok) { |
| 126 | try { |
| 127 | const err = await resp.json() as PinmeResponse; |
| 128 | return { error: err.data && typeof err.data === 'object' && 'error' in err.data |
| 129 | ? (err.data as { error: string }).error |
| 130 | : err.msg || `HTTP ${resp.status}` }; |
| 131 | } catch { |
| 132 | return { error: `HTTP ${resp.status}` }; |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | const result = await resp.json() as PinmeResponse<T>; |
| 137 | if (result.code !== 200) { |
| 138 | return { error: result.data && typeof result.data === 'object' && 'error' in result.data |
| 139 | ? (result.data as { error: string }).error |
| 140 | : result.msg }; |
| 141 | } |
| 142 | return { data: result.data as T }; |
| 143 | } |
| 144 | ``` |
| 145 | |
| 146 | ### Usage Example |
| 147 | |
| 148 | ```typescript |
| 149 | const baseUrl = env.BASE_URL ?? 'https://pinme.cloud'; |
| 150 | |
| 151 | // Send email |
| 152 | const emailResult = await callPinmeAPI<{ ok: boolean }>( |
| 153 | `${baseUrl}/api/v4/send_email`, env.API_KEY, |
| 154 | { to: 'user@example.com', subject: 'Hello', html: '<p>Hi</p>' }, |
| 155 | ); |
| 156 | if (emailResult.error) return json({ error: emailResult.error }, 500); |
| 157 | ``` |