Import from Resend

The dashboard can read your Resend account and set the same things up in Avelto: your domains, your templates, your webhook endpoints and, if you want them, your suppressed and unsubscribed addresses. You see a preview of everything first, tick what to bring across, and nothing is written until you press Import. Then you change your code to send through Avelto.

The import is under Settings, at Import from Resend (/dashboard/settings/import). Only an account owner can run it, because everything it creates is live configuration: domains, templates and webhook endpoints have no test mode, and a live send uses them.

1. Create a key for the import

In Resend, create an API key with Full access and paste it into the import. A key with sending access only can send email and nothing else, so it cannot read your domains or templates, and the import says so.

The key is used only while Avelto reads your Resend account during the preview. It is held in memory for that request and then dropped: it is never stored, never logged and never shown again. Calls go only to Resend's API, and are paced so the import does not use up the rate limit your own sending shares. Delete the key in Resend once you are done.

2. Preview

The preview lists what Avelto found and what it would do with each item, with a checkbox beside everything it can import. Nothing is written. A preview can be imported for 15 minutes; after that, paste the key again.

Running the import again later is safe: anything already on your Avelto account (a domain of the same name, a template with the same slug, an endpoint with the same URL, an address already suppressed) is skipped and left as it is.

3. Import

Press Import and the ticked items are created through the same paths as the rest of the dashboard, with the same limits and checks, and each one is recorded in Settings > Activity like any other change. The result lists every item as created, skipped or failed, with the reason.

Domains

Each domain is added the way Domains > Add domain adds one. Avelto first gives you one TXT record at _avelto.<your domain> that proves the domain is yours; once it is found, the three DKIM records follow, and the SPF and DMARC records sit beside them. The Domains page explains each record.

The preview shows your Resend records beside what Avelto needs. Both sets can be published at the same time, so mail keeps flowing while you switch:

  • Keep the Resend records until the domain shows Verified in Avelto and your code is sending through Avelto. Remove them after that.
  • A name can have only one SPF record. If your domain already has one at the root, add Avelto's include: to it rather than publishing a second record.

A domain already on your account is skipped. One registered on another Avelto account, or one Avelto keeps for itself, is reported and not added. Your plan's domain limit applies, and anything past it is shown and left unticked.

Templates

Each template is copied into an Avelto template with the same name, and its alias becomes the slug (lower case, with hyphens), so template_slug in your code can stay close to what it was. Variables are converted to Avelto's syntax, and anything the import cannot convert exactly is flagged instead of guessed:

In the old templateIn AveltoNotes
{{{NAME}}}{{NAME}}HTML-escaped in the HTML part. Change it to {{{NAME}}} after importing if the value carries markup on purpose.
{{{ ORDER_ID }}}{{ORDER_ID}}Spaces inside the braces are fine.
{{{NAME|there}}}{{NAME}}The fallback is dropped: Avelto has none, so pass NAME on every send.
{{{FIRST_NAME}}}{{FIRST_NAME}}Was filled in from contact data. Pass it in variables yourself.
{{{#if VIP}}}...{{{/if}}}Not importedConditionals and loops are not supported.
{{{contact.first_name}}}Not importedProperty lookups are not supported.
{{{formatDate DATE}}}Not importedHelpers are not supported.
{{NAME}}Not importedTwo braces were not a variable in the old template, so it is flagged for you to check.

A template with a problem is listed with every problem it has and is not imported; fix it in Resend and preview again, or recreate it in Avelto by hand. Avelto templates carry the subject and body only, so a from or reply_to set on the old template is not copied: pass it on each send. Avelto has no fallback values, and a send that leaves out a variable is refused rather than sent with a gap, so pass every variable. See Templates for the syntax.

Webhooks

Each endpoint is recreated with the same URL and the Avelto events that match its events:

Old eventAvelto events
email.sentemail.sent
email.deliveredemail.delivered
email.delivery_delayedemail.delivery_delayed
email.bouncedemail.bounced
email.complainedemail.complained
email.failedemail.failed and email.rejected

These events have no Avelto equivalent, and the preview lists them for each endpoint:

Old eventWhy it is not recreated
email.openedAvelto does not track opens.
email.clickedAvelto does not track clicks.
email.scheduledAvelto sends no event when an email is scheduled; the send response confirms it, and a cancelled one sends email.cancelled.
email.suppressedAvelto refuses a send to a suppressed address in the API response (recipient_suppressed), so there is no event to send.
email.receivedAvelto does not receive email.
domain.*Avelto sends no webhook for domain changes; it emails you when a domain is verified.
contact.*Avelto has no contacts.
suppression.*Avelto sends no webhook for suppression list changes; the email.bounced or email.complained event that caused one comes first.
topic.*Avelto has no topics.
inbox.*Avelto does not receive email.

An endpoint none of whose events has an equivalent is not recreated. The URL must pass the same checks as any Avelto endpoint (in production, https and a public address), and an account can have up to 10 endpoints.

Suppressed and unsubscribed addresses

If the key can read them, the preview counts the addresses on your Resend suppression list (by reason: bounce, complaint or manual) and the contacts marked unsubscribed. Importing them is optional and unticked by default: adding an address to the Avelto suppression list stops all mail to it from this account, until you remove it. Bounces and complaints keep their reason; unsubscribes are added as manual entries.

The preview reads the first 5,000 suppressed addresses and checks the first 2,000 contacts; if there are more, it says so, and the rest stay on the old list only. The Suppressions page explains what a send to a suppressed address returns.

API keys

No provider lets you read an API key back after it was created, so keys are not imported. Create Avelto keys in API keys: a test key while you switch, and a live key for production. Make the production key send-only unless that server also reads the email log.

What to change in your code

Point at Avelto

  • Send requests to https://api.avelto.dev/v1 in place of https://api.resend.com: the send is POST /v1/emails.
  • Put an Avelto API key in AVELTO_API_KEY in place of the old key variable. Use a test key (av_test_) while you switch; production needs a live key, and a send-only one is best.
  • Errors come back as { "error": { "code", "message" } }.

Swap the SDK

  • Node: npm install @avelto/sdk
  • Python: pip install avelto
  • Go: go get github.com/aveltohq/avelto-go
  • PHP: composer require avelto/avelto-php

Check the send request

  • from, to (up to 50), cc, bcc, subject, html and text stay as they are.
  • reply_to takes one address, not a list.
  • tags are strings such as "category:welcome", not { name, value } objects: at most 10, using letters, numbers and _ - : ..
  • Attachments keep filename, content (base64) and content_type; a file fetched by URL is url in place of path. There is no content_id for inline images. At most 10 files and 7 MB in total.
  • A stored template is sent with template_id or template_slug, and variables beside it at the top level, in place of template: { id, variables }. Avelto has no fallback values, so pass every variable.
  • scheduled_at must be an ISO 8601 time, at most 30 days ahead.
  • Custom headers are limited to X- headers and a short list such as In-Reply-To and References; for one-click unsubscribe pass unsubscribe_url.
  • There is no topic_id. If you pass a React component, render it to HTML first and send html.
  • A batch is POST /v1/emails/batch with { "messages": [...] } (up to 100), not a bare array, and it answers message by message. The Idempotency-Key header works on both.

Verify webhooks with Avelto-Signature

  • Imported endpoints start switched off. Change the handler to check the Avelto-Signature header (t=<unix seconds>,v1=<hex>, an HMAC-SHA256 of <t>.<raw body> with the endpoint's secret) in place of the old signature headers, deploy it, then switch the endpoint on.
  • Event names stay the same for the events Avelto has. The body is { id, type, created_at, data } with data.email_id, data.to, data.subject and data.status; de-duplicate on id.

Move the DNS last

  • Publish the Avelto records for each domain. Remove the old records only once the domain shows Verified in Avelto and your code is sending through Avelto.

All four SDKs are at version 0.2.0. The Node SDK and Python SDK pages cover every method, and the Go and PHP quickstarts show those two in use.