| title | Quickstart |
|---|---|
| description | Create a template, send it from your app, then change the copy without a redeploy. |
| icon | rocket |
In about five minutes you'll do the one thing that makes SenderKit different:
create a message template in the dashboard, wire up a single send call, and then
change the wording from the dashboard — no code change, no redeploy. The subject,
layout, and copy live outside your repo; your code just names the template and fills
in the variables.
```text Template copy (in the dashboard editor)
Subject: Welcome to Acme, {{name}} 👋
Hi {{name}},
Thanks for signing up — we're glad you're here.
```
The slug `welcome` is the stable name your code will send to. Staring at a blank
editor? [AI authoring](/concepts/ai-authoring) drafts the subject, layout, and
variables from a one-line brief.
<Tip>
You don't need to publish yet. In test mode SenderKit renders your **latest
draft**, so you can keep editing copy as you go. Live sends only ever render the
[published version](/concepts/versioning).
</Tip>
<Tabs>
<Tab title="TypeScript SDK">
Install the SDK:
<CodeGroup>
```bash npm
npm install @senderkit/sdk
```
```bash pnpm
pnpm add @senderkit/sdk
```
```bash yarn
yarn add @senderkit/sdk
```
```bash bun
bun add @senderkit/sdk
```
</CodeGroup>
Keep your key in the environment (out of source), then create a client and send:
```bash
export SENDERKIT_API_KEY="sk_test_..."
```
```ts
import { SenderKit } from "@senderkit/sdk";
const senderkit = new SenderKit({ apiKey: process.env.SENDERKIT_API_KEY! });
const result = await senderkit.send({
template: "welcome",
to: "user@example.com",
vars: { name: "Ada" },
});
console.log(result); // { id: "msg_…", status: "queued", livemode: false }
```
The core SDK has zero runtime dependencies and uses native `fetch` (Node.js 18+).
Keep the key server-side — never ship it in a client bundle.
</Tab>
<Tab title="cURL">
```bash
curl -X POST https://api.senderkit.com/v1/send \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"template": "welcome",
"to": "user@example.com",
"vars": { "name": "Ada" }
}'
```
Returns `202 Accepted`:
```json
{ "id": "msg_…", "status": "queued", "livemode": false }
```
</Tab>
<Tab title="CLI">
```bash
npm install -g @senderkit/cli
senderkit login # paste your sk_test_… key when prompted
senderkit send welcome user@example.com --vars '{"name":"Ada"}'
```
```
✓ Queued message msg_…
id: msg_…
status: queued
mode: test
```
See the full [CLI reference](/cli/send) for options like `--metadata` and
`--scheduled-at`.
</Tab>
</Tabs>
`vars` fills the `{{name}}` holes in your template. Sends are asynchronous: the call
returns `status: "queued"` once SenderKit accepts the message (or `"scheduled"` if you
passed a future `scheduledAt`), and delivery happens out of band.
```ts
const message = await senderkit.messages.get(result.id);
console.log(message.status, message.timeline);
```
You can also list recent sends with `senderkit.messages.list({ template: "welcome" })`
(CLI: `senderkit messages list`; HTTP: `GET /v1/messages`). See
[Messages](/concepts/messages) for how a send moves from queued to delivered.
That's the whole idea: `send({ template: "welcome" })` is a stable contract, and
everything that changes without engineering — the words, the layout, the subject —
lives on the dashboard side of it. Anyone on the team can fix a typo without opening
your editor.