Migrate from Plunk
Plunk's transactional send is Sendly's ancestral shape, so the request and response bodies are unchanged. Only the URL and the key move.
Last updated 2026-09-04
The closest thing to a drop-in
Sendly grew out of Plunk, and the transactional send is where that shows most directly: POST /v1/send is a near passthrough. The request body is unchanged, the response mirrors Plunk's native shape, and the error body keeps Plunk's fields.
| Setting | Value |
|---|---|
| Endpoint | https://api.sendly.now/api/compat/plunk/v1/send |
| Auth | Authorization: Bearer sk_… |
| Response | { "success": true, "emails": [...], "timestamp": ... } |
| Errors | { code, error, message, time } |
A client reading body.message or body.error on failure keeps working. So does one that checks body.success on the way through.
The change
await fetch("https://api.useplunk.com/v1/send", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.PLUNK_SECRET_KEY}`,
},
body: JSON.stringify({
to: "customer@example.com",
subject: "Hello",
body: "<p>Welcome aboard.</p>",
}),
});await fetch("https://api.sendly.now/api/compat/plunk/v1/send", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.SENDLY_API_KEY}`,
},
body: JSON.stringify({
to: "customer@example.com",
subject: "Hello",
body: "<p>Welcome aboard.</p>",
}),
});That is the whole diff. If the URL and the key already come from configuration, there is no code change at all.
Supported fields, and retries
| Field | Support | Note |
|---|---|---|
to | Full | A string or an array. |
subject | Full | |
body | Full | HTML or plain text. |
Idempotency-Key | Full | 1 to 255 characters. Send the same key again and the send is not duplicated. |
The idempotency header is worth adopting even if your current sender does not use it. A retry after a network timeout is the classic way one customer receives the same receipt twice, and a stable key per logical message makes that impossible rather than unlikely.
Sending still requires a from address on a verified domain belonging to the project the key belongs to — that is a Sendly rule, and the near-passthrough does not exempt it.
Everything past transactional sending
The compat endpoint covers the send path. Contacts, segments, templates, campaigns, workflows, webhooks and events are Sendly's own API, which is a larger surface than Plunk's and worth reading rather than guessing at: the REST reference is the complete contract, and the official JavaScript and Python SDKs are generated from it.
Verify your domain and import your suppression list before the first real send, then move one slice at a time — the staged rollout plan has the order. The technical guide is the Plunk migration page in the docs.
Questions people ask
- Is anything about the request body different?
- No. Plunk's transactional send is Sendly's ancestral shape, so to, subject and body are unchanged and the response mirrors Plunk's native one. Only the URL and the key move.
- Does the Idempotency-Key header work?
- Yes, with keys of 1 to 255 characters. Sending the same key twice does not produce a second email, which is what makes a retry after a network timeout safe.
- Do I still need a verified domain?
- Yes. The near-passthrough covers the request shape, not the sending rules: the from address must be on a verified domain belonging to the key's project. Guided DNS setup can publish the records for you.
- What about Plunk contacts and campaigns?
- Those are not part of the compat endpoint. Use Sendly's own API for contacts, segments, templates, campaigns, workflows and events — it is a larger surface, and the official SDKs cover all of it.
Something on this page out of date, or missing the case you are in? Write to support@sendly.now — we answer our own email. The full technical detail lives in the documentation.