Migration

Migrate from SendGrid

Keep @sendgrid/mail. Point its transport at Sendly's SendGrid dialect, swap the key, and personalizations, attachments and headers keep working.

Last updated 2026-09-04

What Sendly implements

SendGrid's POST /v3/mail/send. A successful send answers 202 with an empty body and an X-Message-Id header, which is the exact semantic @sendgrid/mail expects — so the SDK's own success path is unchanged, not merely compatible.

SettingValue
Base URL / hosthttps://api.sendly.now/api/compat/sendgrid
AuthAuthorization: Bearer sk_…
Success202

The change

One ordering detail decides whether this works: the base URL lives on @sendgrid/client, and setApiKey resets it. Set the key first and the base URL second, or the transport silently goes back to SendGrid.

JavaScript — order matters
import sgMail from "@sendgrid/mail";
import sgClient from "@sendgrid/client";

sgClient.setApiKey(process.env.SENDLY_API_KEY);
sgClient.setDefaultRequest("baseUrl", "https://api.sendly.now/api/compat/sendgrid");
sgMail.setClient(sgClient);

// every call site below is untouched
await sgMail.send({
  to: "customer@example.com",
  from: "you@yourdomain.com",
  subject: "Hello",
  html: "<p>Welcome aboard.</p>",
});
Python
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail

message = Mail(
    from_email="you@yourdomain.com",
    to_emails="customer@example.com",
    subject="Hello",
    html_content="<p>Welcome aboard.</p>",
)

client = SendGridAPIClient(
    api_key=os.environ["SENDLY_API_KEY"],
    host="https://api.sendly.now/api/compat/sendgrid",
)
client.send(message)

What carries over

FieldSupportNote
personalizations[]FullEach personalization fans out into its own send, carrying its own to, cc, bcc and subject.
content[]FullThe text/html entry is preferred as the body, falling back to text/plain.
fromFullObject form with email and name, same as reply_to. The address must be on a verified domain.
attachmentsFullIncluding type as the content type and content_id for inline images.
headersFullPassed through.
categoriesPartialMapped onto Sendly tags, sanitized.

What does not come across

Two fields return a clean SendGrid-shaped 400 rather than being accepted and dropped.

  • Dynamic template ids — the d-… ones. A SendGrid template id refers to content stored in SendGrid, which Sendly cannot resolve. Rebuild the template in Sendly and send it by Sendly template id, or move the markup into the request body.
  • send_at. Scheduled sends are not supported through the compat path in this phase; schedule on your side, or use Sendly's native send.

Errors come back in SendGrid's { errors: [{ message, field, help }] } shape with the matching status, so a thrown ResponseError in JavaScript and an HTTPError in Python fire exactly as they do today.

If your account leans on dynamic templates, be honest with yourself about the size of that job before you plan a date. Rebuilding templates is the real cost of this migration, and it is not in the SDK change.

Before you switch a production sender

Verify your domain and import your suppression list first, then move one slice at a time. The staged rollout plan is the seven steps we would follow, in the order that keeps each one reversible.

The full supported-field table lives in the SendGrid migration guide in the docs.

Questions people ask

Why does setting the base URL before the key not work?
Because setApiKey on @sendgrid/client resets the default request, base URL included. Set the key first, then the base URL, then hand the client to sgMail. Getting it the wrong way round sends your mail to SendGrid with a Sendly key, which fails as an auth error rather than as anything that points at the real cause.
Do personalizations still fan out?
Yes. Each personalization becomes its own send carrying its own to, cc, bcc and subject, which is the behaviour the field exists for.
What happens to my categories?
They map onto Sendly tags, sanitized. It is a partial mapping rather than an identity, so once you have moved, treat the tags Sendly reports as the source of truth.
Can I keep using dynamic templates during the move?
Not through the compat endpoint — a request carrying template_id gets a 400 in SendGrid's own error shape. Move template-driven traffic last, after you have rebuilt those templates in Sendly, and leave it on SendGrid until then.

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.