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 template | In Avelto | Notes |
|---|---|---|
{{{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 imported | Conditionals and loops are not supported. |
{{{contact.first_name}}} | Not imported | Property lookups are not supported. |
{{{formatDate DATE}}} | Not imported | Helpers are not supported. |
{{NAME}} | Not imported | Two 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 event | Avelto events |
|---|---|
email.sent | email.sent |
email.delivered | email.delivered |
email.delivery_delayed | email.delivery_delayed |
email.bounced | email.bounced |
email.complained | email.complained |
email.failed | email.failed and email.rejected |
These events have no Avelto equivalent, and the preview lists them for each endpoint:
| Old event | Why it is not recreated |
|---|---|
email.opened | Avelto does not track opens. |
email.clicked | Avelto does not track clicks. |
email.scheduled | Avelto sends no event when an email is scheduled; the send response confirms it, and a cancelled one sends email.cancelled. |
email.suppressed | Avelto refuses a send to a suppressed address in the API response (recipient_suppressed), so there is no event to send. |
email.received | Avelto 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/v1in place ofhttps://api.resend.com: the send isPOST /v1/emails. - Put an Avelto API key in
AVELTO_API_KEYin 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,htmlandtextstay as they are.reply_totakes one address, not a list.tagsare strings such as"category:welcome", not{ name, value }objects: at most 10, using letters, numbers and_ - : ..- Attachments keep
filename,content(base64) andcontent_type; a file fetched by URL isurlin place ofpath. There is nocontent_idfor inline images. At most 10 files and 7 MB in total. - A stored template is sent with
template_idortemplate_slug, andvariablesbeside it at the top level, in place oftemplate: { id, variables }. Avelto has no fallback values, so pass every variable. scheduled_atmust be an ISO 8601 time, at most 30 days ahead.- Custom
headersare limited toX-headers and a short list such asIn-Reply-ToandReferences; for one-click unsubscribe passunsubscribe_url. - There is no
topic_id. If you pass a React component, render it to HTML first and sendhtml. - A batch is
POST /v1/emails/batchwith{ "messages": [...] }(up to 100), not a bare array, and it answers message by message. TheIdempotency-Keyheader works on both.
Verify webhooks with Avelto-Signature
- Imported endpoints start switched off. Change the handler to check the
Avelto-Signatureheader (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 }withdata.email_id,data.to,data.subjectanddata.status; de-duplicate onid.
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.