@playcode/sdk
v0.1.0
Published
Official SDK for Playcode App Services. Zero dependencies, fetch-based, typed. Today: Playcode Email.
Maintainers
Readme
@playcode/sdk
The official SDK for Playcode App Services - the product APIs your app
calls at runtime. Email today, storage next. Zero runtime dependencies,
fetch-based, fully typed, MIT.
npm install @playcode/sdkQuickstart
import { Playcode } from '@playcode/sdk'
const pc = new Playcode() // reads PLAYCODE_SECRET_KEY
const { status, messageId } = await pc.email.send({
to: '[email protected]',
subject: 'Reset your password',
html: '<a href="https://app.example.com/reset?t=...">Reset your password</a>',
})Works inside a Playcode-hosted app with zero configuration (the platform injects the key) and anywhere else with one env var.
Environment
| Variable | Default | Purpose |
| --- | --- | --- |
| PLAYCODE_SECRET_KEY | - | Your app's pcsk_... key. Server-side only - never ship it to a browser. |
| PLAYCODE_EMAIL_URL | https://email.playcode.io | Email front door. Only set it to target a non-production environment. |
Both are read at call time, so exporting them after the client is constructed still works (dotenv, secret managers, tests). Everything is overridable:
const pc = new Playcode({ apiKey, emailUrl, timeoutMs: 30_000 })new Playcode() never throws and never touches the network - a missing key
fails on the call that needs it, so the client is safe at module scope.
Capture-first. Every app starts in capture mode: email is stored, not
delivered, and appears in the Playcode IDE's Mailbox panel with every link
clickable - so password-reset and verification flows are testable before a
single message can reach a stranger. status tells you what the platform did:
| status | Meaning |
| --- | --- |
| captured | Stored, not delivered. Open the Mailbox panel in the IDE. |
| withheld | Blocked by quota, abuse or reputation policy. |
| delivered | Handed to the delivery pipeline. |
Delivery arrives through this same call as your app graduates - no code change. New statuses are added additively, so treat an unknown one as "not delivered yet", never as an error.
await pc.email.send(
{
to: ['[email protected]', '[email protected]'], // string or string[]
subject: 'Your invoice',
html: '<p>Thanks!</p>',
text: 'Thanks!', // html and/or text
from: '[email protected]', // defaults to the app sender
replyTo: '[email protected]',
headers: { 'X-Entity-Ref-ID': invoice.id },
idempotencyKey: job.id, // same key = sent once, ever
},
{ timeoutMs: 30_000 }, // per call; default 10s
)Set idempotencyKey from a job or request id and retries stop being scary: the
platform returns the original message instead of sending twice. The SDK never
retries a send on its own - that decision is yours.
Nothing is validated client-side. The platform owns validation, and its messages come back verbatim.
Errors
import { Playcode, PlaycodeApiError, PlaycodeConnectionError } from '@playcode/sdk'
try {
await pc.email.send({ to, subject, html })
} catch (error) {
if (error instanceof PlaycodeApiError) {
// The platform said no. `message` is its own wording, untouched.
console.error(error.status, error.code, error.message, error.requestId)
if (error.status === 429) return scheduleRetryLater()
}
if (error instanceof PlaycodeConnectionError) {
// Never reached it (DNS, TLS, socket, timeout). `cause` has the original.
return retry()
}
throw error
}| Error | When | Carries |
| --- | --- | --- |
| PlaycodeApiError | Non-2xx from the platform | status, code, message (verbatim), requestId |
| PlaycodeConnectionError | Never got an answer, incl. timeout | message, cause |
| PlaycodeConfigError | No API key, at call time | message naming PLAYCODE_SECRET_KEY |
Subpath import
Pull in only what you use:
import { sendEmail } from '@playcode/sdk/email'
await sendEmail({ to: '[email protected]', subject: 'Hi', text: 'Hi' })Same wire call, same errors, same config resolution as pc.email.send.
Notes
- Node 18+ (global
fetch,AbortSignal.timeout). ESM and CommonJS builds ship side by side. PLAYCODE_SECRET_KEYauthenticates your app to Playcode. Keep it on the server. A leaked key can send email as your app.- This package is the customer surface. Playcode's internal Sky infrastructure
SDK (
@playcode/sky) is private and unrelated - any future VM control lands here as a Playcode product API, not as raw Sky access.
MIT.
