Skip to content

Moving from Resend

Duta’s API follows Resend’s, so an application using a Resend SDK moves by changing configuration. The code stays as it is.

Set the API key to a Duta key. Each SDK then takes Duta’s address in its own way:

Terminal window
RESEND_API_KEY=duta_xxxxxxxxxxxxxxxx
RESEND_BASE_URL=https://api.duta.indra.sh

Or in code: new Resend(key, { baseUrl: 'https://api.duta.indra.sh' }).

  1. Add your domain in Duta and publish its two records. Duta uses the duta. label, not Resend’s send., so your Resend records stay live while you move and nothing breaks in between. See Domains.

  2. Create a Duta API key. A key is send only unless you ask for full_access. Ask for it if the application also manages domains, keys or webhooks.

  3. Point your webhook at Duta if you use one. Deliveries are signed the way Resend signs them (Svix headers, whsec_ secret), so resend.webhooks.verify(...) and the Svix libraries verify them unchanged. Use the secret Duta shows when you add the endpoint.

  • emails.send, including tags, cc, bcc, replyTo (mapped to reply_to on the wire), headers and attachments given as base64, a Buffer or a path URL. A missing content type is inferred from the filename. React Email (react: <Component />) works in the Node SDK because the SDK renders HTML client-side before sending.
  • batch.send, strict by default: one invalid email and none are sent. batchValidation: 'permissive' (or the x-batch-validation: permissive header) sends the valid ones and lists the rest in errors by index.
  • emails.get, emails.list, domains.list, domains.get, domains.create, domains.verify, domains.remove, apiKeys.list, apiKeys.create, apiKeys.remove, webhooks.create and webhooks.list.
  • idempotencyKey, for 24 hours per account.
  • Errors carry Resend’s name, statusCode and message, so code that branches on error.name keeps working. Duta adds a finer-grained code and request_id, which finds the request on the Logs screen.
  • Webhook event names: email.sent, email.delivered, email.bounced, email.complained, email.delivery_delayed and email.failed.
  • Refused plainly rather than ignored: scheduledAt (scheduled_at), template, topicId (topic_id) and API keys limited to one domain (domain_id). Duta answers 422 naming the field, so nothing goes out at the wrong time or with a wider key than you asked for.
  • Not in Duta: audiences, contacts, broadcasts, segments, templates, receiving and SMTP. Duta is transactional sending over the API.
  • Region: every domain sends from ap-southeast-1. A region given when adding a domain is accepted and ignored, and the response says which region is used.
  • Tracking: Duta does not track opens or clicks, so email.opened and email.clicked are accepted on a webhook but never sent.
  • Send status: POST /emails answers 202 with { id, status }. The SDKs treat it as success like Resend’s 200. Only raw HTTP code that checks for exactly 200 needs a change.
  • Attachments in a batch: inline content only. path is refused there.
  • Attachment size and types: Free sends take no attachments. Starter allows up to 1 MB of attachments per message, Pro allows 2 MB, and Custom allows 5 MB, with an overall 10 MB message ceiling. Files must be on the allowed types list (PDF, images, CSV, text, Office documents).
  • Free plan: sends only from your sandbox address to recipients who confirmed, 10 a day, without attachments. Any paid plan sends from your own domain.
  • Timestamps: every timestamp the API returns is an ISO 8601 string, as Resend’s are.