SendlySendly
Migrate from

Migrate from Resend to Sendly

Keep the official Resend SDK for JavaScript or Python. Point it at Sendly's Resend-compatible API by changing the base URL and API key, and see which fields carry over.

Sendly implements Resend's POST /emails, POST /emails/batch, and GET /emails/:id endpoints. Keep the official resend SDK — change the base URL and the API key.

  • Base URL: https://api.sendly.now/api/compat/resend
  • Auth: Authorization: Bearer <sk_...> (the SDK sets this for you)

No Sendly key yet? Create a free account, verify a sending domain, then create a key in your project's API key settings. Still deciding? Sendly vs Resend compares the two products, migrating from Resend answers the questions teams ask before switching, and the staged rollout plan covers moving traffic over in steps.

The two-variable swap

Before (Resend)
import { Resend } from "resend";

const resend = new Resend("re_your_resend_key");

await resend.emails.send({
  from: "you@yourdomain.com",
  to: "customer@example.com",
  subject: "Hello",
  html: "<p>Welcome aboard.</p>",
});
After (Sendly)
import { Resend } from "resend";

const resend = new Resend("sk_your_sendly_key", {
  baseUrl: "https://api.sendly.now/api/compat/resend",
});

await resend.emails.send({
  from: "you@yourdomain.com",
  to: "customer@example.com",
  subject: "Hello",
  html: "<p>Welcome aboard.</p>",
});

The SDK also honors the RESEND_BASE_URL environment variable, so you can point it at Sendly without touching code:

RESEND_BASE_URL="https://api.sendly.now/api/compat/resend"
Before (Resend)
import resend

resend.api_key = "re_your_resend_key"

resend.Emails.send({
    "from": "you@yourdomain.com",
    "to": "customer@example.com",
    "subject": "Hello",
    "html": "<p>Welcome aboard.</p>",
})
After (Sendly)
import resend

resend.api_key = "sk_your_sendly_key"
resend.api_url = "https://api.sendly.now/api/compat/resend"

resend.Emails.send({
    "from": "you@yourdomain.com",
    "to": "customer@example.com",
    "subject": "Hello",
    "html": "<p>Welcome aboard.</p>",
})

The Python SDK also reads the RESEND_API_URL environment variable.

API reference

Resend's dialect is documented operation by operation, with the request and response schemas Sendly accepts and returns:

Supported fields

FieldSupportNotes
fromFullPlain address or "Name <you@domain.com>" display-name form.
toFullString or array.
cc / bccFullString or array.
subjectFull
html / textFullhtml is preferred when both are present. At least one is required.
reply_toPartialThe first address is used.
headersFull
attachmentsPartialInline base64 content only (see below).
tagsPartialBest-effort: name/value are combined and sanitized, max 10.
emails.get(id)FullReturns the Resend email object with a mapped last_event. Needs a key with emails:read — see below.
Batch sendFullOrder is preserved in the response. Up to 100 emails per request; a longer array is refused with 422 validation_error and nothing is sent.

Not supported (returns a clean Resend error)

FieldResult
scheduled_at422 validation_error — scheduled sends aren't supported in this phase.
attachments[].path422 — Sendly does not fetch attachments from a URL; send the file inline as base64 content.
template422 — Resend template IDs can't be mapped to Sendly templates.
topic_id422 — broadcast topics aren't supported.

Each unsupported field returns Resend's own { statusCode, message, name } error body, so your existing resend error handling ({ data, error } in JS, a raised ResendError in Python) behaves exactly as it does today.

Key permissions

Sending works with any key that may send. emails.get(id) is the one Resend call Sendly maps to something other than a send, and it is gated on emails:read instead — reading returns the message's subject, body and recipient, so it follows the same permission rule here as it does on the native API.

A key with full permissions covers both and needs nothing changed. A key created with only sending ticked keeps sending normally and gets this on the read:

{
  "statusCode": 403,
  "message": "This API key is missing the required scope: emails:read",
  "name": "restricted_api_key"
}

Grant the key emails:read in your project's API key settings, or use a full-permission key.

On this page