# Resend

Source: https://developers.swell.is/app-guides/resend

The Resend app sends your store's notification emails through your own Resend account, from an address on your own domain. It uses the notification templates you already have in **Settings > Notifications**, so there's no second set of templates to keep up to date.

Each email the app covers has its own setting, and **Custom notifications** sends other notification templates on events you choose. An email is sent through Resend only after you turn off Swell's own version of it.

## How it works

| When | What the app does |
| --- | --- |
| An order, shipment, refund, account, cart or subscription event happens, and its email is turned on | Reads the notification from Settings > Notifications, renders it with the order, customer, cart or subscription, and sends it through Resend to the notification's recipient. |
| Swell's own version of that email is still on | Skips the email and logs why, so the customer doesn't get two copies. |
| An event matches a row in Custom notifications | Sends the named notification, without checking whether Swell also sends it. |
| Resend can't be reached, rate-limits the app or has a server error | Fails the event, so Swell delivers it again later. Emails the event already sent aren't sent again within 24 hours. |
| Resend rejects an email, for example because the domain isn't verified | Logs the error and doesn't try again. |

## Before you begin

- A Resend account with a verified domain to send from.
- Your notifications set up in **Settings > Notifications**. The app sends each one with its own subject, content and template, and to the same recipient as Swell.

## Set up

Install Resend from the Swell App Store, and set it up in your test environment before the live one.

**1. Verify your domain**

In Resend, open **Domains**, add the domain you send email from, and add the DNS records Resend lists at your DNS provider. Resend sends email only from verified domains.

**2. Create an API key**

In Resend, open **API Keys** and create a key with **Sending access**. Copy the key, which starts with `re_`.

**3. Add them to the app**

In Swell, go to **Apps > Resend > Settings** and enter the **API Key** and the **From Address**, an address on your verified domain such as `orders@yourstore.com`. To show a sender name, enter **From Name**. To send customer replies to another address, enter **Reply-To Address**. Click **Save settings**.

**4. Choose the emails**

On the same page, turn on each email you want Resend to send and click **Save settings**. **Order confirmation**, **Shipping confirmation** and **New subscription** are on at first. Shipping and abandoned cart emails work differently; see **Shipping and abandoned cart emails**.

**5. Turn off Swell's own emails**

Go to **Settings > Notifications**, turn off Swell's version of each email you turned on in the app, and click **Save changes**. Until you do, Swell keeps sending the email and the app skips it. You can switch one email at a time. Swell's own **Subscription invoice** is off unless you turned it on. For abandoned cart emails, turn off the **Abandoned cart** switch. Shipping emails are turned off through the Swell API; see **Shipping and abandoned cart emails**.

**6. Place a test order**

Place an order with your own email address. With **Order confirmation** switched to Resend, one order confirmation arrives, from your **From Address**, and Resend lists it under **Emails**. Check that it looks the way you expect. If no email arrives, see **Troubleshooting**.

**7. Go live**

Settings are saved separately for each environment, and so is each notification you turn off in **Settings > Notifications**. In the live environment, repeat steps 3 to 5.

## Using Resend

### Emails the app sends

Each email has a setting in **Apps > Resend > Settings**, and uses the template of the Swell notification in the last column.

| App setting | Sent when | Swell notification |
| --- | --- | --- |
| Order confirmation | An order is placed, or a draft order is submitted. Orders created with notify set to false are skipped. | Order confirmation |
| Shipping confirmation | A shipment is created, unless it's a draft. | Shipping confirmation |
| Shipping update | A shipment's tracking number is set or changed, unless the shipment is a draft. Other edits to a shipment are skipped. | Shipping update |
| Order canceled | An order is canceled. | Order canceled |
| Order refund | A refund succeeds. The email lists every refund on the order. | Order refunded |
| Customer welcome | A customer account is created with a password. Guest accounts are skipped. | Customer welcome |
| Abandoned cart recovery | A cart is abandoned. Carts without a customer account or without items are skipped. | Abandoned cart |
| New subscription | A subscription is created active. | New subscription |
| Subscription canceled | A subscription is canceled. | Subscription canceled |
| Subscription paused | A subscription is paused, unless it's canceled. | Subscription paused |
| Subscription resumed | A paused subscription resumes, unless it's canceled. | Subscription resumed |
| Subscription invoice | A subscription invoice is created, unless the subscription total is 0. The email shows the latest invoice. | Subscription invoice |

### Switching an email to Resend

An email is sent through Resend when its setting in the app is on and Swell's own version is off. For most emails, you turn Swell's version off in **Settings > Notifications**. Shipping and abandoned cart emails are turned off differently. While Swell's version is on, the app skips the email and logs that Swell's email is still on, so customers never get both. If Swell's version is off and the app's setting is off too, neither Swell nor the app sends the email.

The app checks Swell's version each time it sends. To switch an email back to Swell, turn Swell's version on again.

### Shipping and abandoned cart emails

Swell's own **Shipping confirmation** and **Shipping update** have no switch in **Settings > Notifications**, so the app skips them until you turn Swell's versions off through the Swell API. Swell sends its own shipping emails when you check **Send email confirmation to customer** on a fulfillment. To turn them off with the Swell CLI, find the notification's ID, then set `enabled` to `false`:

```sh
swell api get "/:notifications?model=orders&name=shipped.v2"
swell api put /:notifications/<notification ID> --body '{"enabled": false}'
```

Repeat both commands for `shipped-update.v2`, and add `--live` in the live environment. Saving **Settings > Notifications** afterwards keeps them off. To switch shipping emails back to Swell, set `enabled` to `true`. Once Swell's versions are off, the app sends a shipping confirmation for every shipment that isn't a draft, whether or not **Send email confirmation to customer** is checked. It sends a shipping update when a shipment's tracking number is set or changed, and not for other edits.

Swell sends its abandoned cart emails while the **Abandoned cart** switch in **Settings > Notifications** is on. To send the abandoned cart email through Resend, turn off the **Abandoned cart** switch and click **Save changes**. Swell then sends no abandoned cart emails, including follow-ups, and the app sends one email for each abandoned cart that has a customer account and items.

### Templates

Each email uses the notification's subject, content fields and template as they are when the email is sent, so your edits in **Settings > Notifications** apply to the next email. The record is loaded with the same related data Swell loads for its own email, and the logo and **Primary color** come from **Branding** in **Settings > Notifications**.

- Prices are formatted in the record's currency.
- Templates are rendered with Liquid. Swell's own `currency` and `img_url` filters are supported, and are the only Swell filters the standard templates use. Other Swell-specific filters aren't supported.

### From, Reply-To and BCC

Each notification in **Settings > Notifications** has an optional **From email** and **BCC emails**, and the app uses them:

- A **From email** on the same domain as the app's **From Address** is the sender, with your **From Name** and **Reply-To Address**.
- A **From email** on any other domain becomes the Reply-To address, and the email is sent from the **From Address**. Resend sends only from verified domains, and Swell's own emails put the **From email** in Reply-To too.
- Without a **From email**, the email is sent from the **From Address**, with the **Reply-To Address** if you entered one.
- Each address in **BCC emails** gets a blind copy. CC addresses set on the notification through the Swell API get a copy.

### Custom notifications

To send another notification through Resend, add a row under **Custom notifications** in **Apps > Resend > Settings**:

- **Trigger event**: the event that sends the email. The list includes events with no built-in email, such as `order.paid`, `order.delivered`, `subscription.paid` and `subscription.trial_will_end`.
- **Notification name**: the notification's name, not its label. The names of Swell's standard notifications end in `.v2`, such as `recovery.v2`.
- **Record ID field**: where the record's ID is in the event. Leave it empty to use `id`.
- **Enabled**: turns the row off without removing it.

The notification must belong to the same kind of record as the event: an `order.` event sends an order notification, a `subscription.` event a subscription notification, and so on. A `shipment.` or `payment.` event looks for a shipment or payment notification, so it can't send an order's shipping or refund email.

Custom rows don't check whether Swell also sends the notification. Turn off Swell's version yourself, or the customer gets two emails.

### When sending fails

When Resend can't be reached, rate-limits the app or has a server error, the app fails the event and Swell delivers it again later, for up to about 4 days. Each email is sent with a key that Resend keeps for 24 hours, so an email that was delivered isn't sent again when the event is retried within that time.

When Resend rejects an email, for example because of an invalid API key or a From Address on a domain that isn't verified, the error is logged and the email isn't retried. Fix the setting; later emails are sent normally. A failed email doesn't stop the other emails for the same event.

### In the dashboard

| Where | What it's for |
| --- | --- |
| Apps > Resend > Settings | The API key and sender, the setting for each email, and Custom notifications. |
| Settings > Notifications | Swell's own emails, which you turn off to send them through Resend, and the templates, From email, BCC emails and Branding the app uses. |
| Developer > Console > Logs | A line for each email the app sends or skips, and each error. |

## Settings

Settings are in **Apps > Resend > Settings** and are saved separately for each environment.

| Setting | Description | Default |
| --- | --- | --- |
| API Key | Your Resend API key, which starts with re_ and needs Sending access. Masked on the settings page. Required. | None |
| From Address | The sender address, on a domain verified in Resend. A notification's From email on the same domain is used instead. Required. | None |
| From Name | The sender name recipients see, such as Acme Store. | None |
| Reply-To Address | The address customer replies go to, unless the notification's From email is on another domain. | None |
| Order confirmation (order.submitted → receipt) | Sends the order confirmation when an order is submitted. | On |
| Shipping confirmation (shipment.created → shipped) | Sends the shipping confirmation when a shipment that isn't a draft is created. Sends only after Swell's version is turned off through the Swell API. | On |
| Shipping update (shipment.updated with a new tracking number → shipped-update) | Sends the shipping update when a shipment's tracking number is set or changed. Sends only after Swell's version is turned off through the Swell API. | Off |
| Order canceled (order.canceled → canceled) | Sends the order canceled email when an order is canceled. | Off |
| Order refund (payment.refund.succeeded → refund) | Sends the refund email when a refund succeeds. | Off |
| Customer welcome (account.created → welcome) | Sends the welcome email when a customer creates an account with a password. | Off |
| Abandoned cart recovery (cart.abandoned → recovery) | Sends the abandoned cart email when a cart with a customer account and items is abandoned. Sends only while the Abandoned cart switch in Settings > Notifications is off. | Off |
| New subscription (subscription.created → new) | Sends the new subscription email when an active subscription is created. | On |
| Subscription canceled (subscription.canceled → canceled) | Sends the subscription canceled email. | Off |
| Subscription paused (subscription.paused → paused) | Sends the subscription paused email. | Off |
| Subscription resumed (subscription.resumed → resumed) | Sends the subscription resumed email. | Off |
| Subscription invoice (subscription.invoiced → invoice) | Sends the subscription invoice email when an invoice is created. | Off |
| Custom notifications | Rows that each send a notification on a trigger event, with Trigger event, Notification name, Record ID field and Enabled. | None |

## Limitations

- Swell's **Shipping confirmation** and **Shipping update** can't be turned off in the dashboard, so switching them to Resend takes the Swell API, as described under **Shipping and abandoned cart emails**.
- The app doesn't see **Send email confirmation to customer** on a fulfillment. It sends a shipping confirmation for every shipment that isn't a draft, and a shipping update only when a tracking number is set or changed.
- The app sends only the first abandoned cart email. Turning off the **Abandoned cart** switch also stops Swell's follow-up emails.
- The app has no setting for **Draft order invoice**, **Gift card**, **Password reset**, **Subscription payment expiring**, **Subscription payment failed**, **Payment confirmation** or the **Order received** email to admins. Keep Swell's own version of these on.
- Every email goes to the notification's one recipient and its BCC emails. A notification's **From email** is the sender only when it's on the same domain as the app's **From Address**. On any other domain, it becomes the Reply-To address.
- A customer gets the welcome email only when the account is created with a password. A guest who sets a password later doesn't get one.
- A notification template that uses one of Swell's Liquid filters other than `currency` and `img_url` may not render the same as Swell's email.
- Protection against sending an email twice lasts 24 hours, and Swell retries a failed event for up to about 4 days. If Swell delivers an event again after 24 hours, an email it already sent can be sent again.
- Custom notifications can send only notifications of the event's own kind of record, and don't check Swell's own emails.

## Troubleshooting

The app logs each email it sends or skips, and each error, in lines that start with `Resend:`. In the Swell dashboard, the logs are under **Developer > Console > Logs**.

### No email is sent and nothing is logged

The app's setting for that email is off. Turn it on in **Apps > Resend > Settings** and click **Save settings**. If Swell's own version is off too, no email is sent at all.

### "missing required settings (api_key, from_email)"

Enter the **API Key** and **From Address** in **Apps > Resend > Settings** and click **Save settings**. No email is sent until both are set.

### "Swell's native … email is still on"

Swell still sends this email, so the app skipped it. Turn off Swell's version in **Settings > Notifications** and click **Save changes**. The log quotes the template's own label, which can differ from the name in **Settings > Notifications**, for example Order Receipt for **Order confirmation**. For abandoned carts, the rest of the line says to turn off the **Abandoned cart** switch. For shipping emails, it says to set `enabled` to `false` through the API. See **Shipping and abandoned cart emails**.

### "shipment is a draft" or "tracking number did not change"

Swell sends no shipping emails for draft shipments, and the app sends a shipping update only when an edit sets or changes the tracking number. The email isn't sent, and there's nothing to fix.

### "cart has no customer account" or "cart is empty"

Swell sends abandoned cart emails only for carts with a customer account and items, and the app does the same. The email isn't sent, and there's nothing to fix.

### "template … not found in store"

No notification with that name belongs to the event's kind of record. For a custom notification, check that **Notification name** is the notification's name, not its label, and that the notification belongs to the same kind of record as the **Trigger event**.

### "no recipient from …"

The record has no address in the field the notification sends to, for example a cart without a customer email. The email isn't sent.

### "Resend 401", "Resend 403" or "Resend 422"

Resend rejected the email, and the message after the status code says why, for example an invalid API key, a From Address on a domain that isn't verified, or an invalid address in a notification's **BCC emails**. Fix the key in **Apps > Resend > Settings**, verify the domain in Resend, or correct the **BCC emails** in **Settings > Notifications**. These errors aren't retried, so the email isn't sent.

### "Resend 429", a 5xx status or "Resend unreachable"

Resend rate-limited the app, had a server error or couldn't be reached. Swell delivers the event again later. A 429 can also mean your Resend plan's daily or monthly limit was reached.

### A customer gets two emails

A custom notification sends a notification that Swell also sends. Turn off Swell's version in **Settings > Notifications**. For abandoned carts, leave the **Abandoned cart** switch off.

### An email looks different from Swell's

Swell's Liquid filters other than `currency` and `img_url` aren't supported. To compare, send Swell's version from the notification's **Test email** tab in **Settings > Notifications**.

## Uninstalling

Uninstalling the app removes its functions and settings, so emails stop going through Resend. Swell's own notifications that you turned off stay off, and customers get no email for them until you turn them on again in **Settings > Notifications**. To keep emails going, turn Swell's versions back on before you uninstall: the **Abandoned cart** switch for abandoned carts, and `enabled` set to `true` through the API for shipping emails. The app skips them from then on. Uninstalling applies to the environment you are in, so remove the app from the test and live environments separately.

Emails already sent stay in your Resend account. If you no longer need the API key, delete it in Resend under **API Keys**.

## For developers

| Function | Runs on | What it does |
| --- | --- | --- |
| order-emails | order.submitted, order.paid, order.canceled and order.delivered | Sends the order confirmation and order canceled emails, and custom notifications on these events. |
| shipment-emails | shipment.created and shipment.updated | Sends the shipping confirmation and update, rendered with the order and the shipment that raised the event. |
| payment-emails | payment.refund.succeeded | Sends the refund email, rendered with the order and every refund on it. |
| account-emails | account.created | Sends the welcome email to accounts with a password. |
| cart-emails | cart.abandoned | Sends the abandoned cart email. |
| subscription-emails | subscription.created, activated, paused, resumed, canceled, invoiced, paid, trial_will_end and trial_ended | Sends the new, paused, resumed, canceled and invoice subscription emails, and custom notifications on these events. |

Every function calls the same handler. It reads the notification config from `/:notifications` by `name` and `model`, because names such as `canceled.v2` and `invoice.v2` exist on more than one model. A built-in email is skipped unless the config's `enabled` is `false`. `recovery.v2` is also sent while `abandoned_cart.enabled` in `/settings/notifications` isn't `true`. The record is loaded with the config's `query.expand`, and the HTML is fetched from `content.html.url`, with a `cdn.swell.test` host replaced by `cdn.swell.store`, and rendered with the browser build of liquidjs. The render context is the record plus `store` (`/settings/store` with the logo, logo width, footer and `store_color` from `/settings/notifications`, which defaults to `#614ed0`, and `name` and `support_email` from `/:clients/:self` when `/settings/store` has none, then from `from_name` and `reply_to`), `content` (the rendered `fields`) and the data Swell's own emails get through `$notify.data`: `refunds`, `shipment` or `invoice`.

Emails are sent with `POST https://api.resend.com/emails` and an `Idempotency-Key` of `swell-resend/<event type>/<event id>/<model>.<name>`. The config's `from`, or the deprecated `replyto`, is the `from` when its domain matches `from_email`, and the `reply_to` otherwise. Its comma-separated `cc` and `bcc` are sent as `cc` and `bcc`. Network errors, 429 and 5xx responses are rethrown so Swell retries the event. Other 4xx responses are thrown with `retry: false`. The app has no models, content views or `$app` fields.

## Support

Swell builds and maintains the Resend app. For help with the app, email support@swell.is. For questions about your Resend account, domains or sending limits, contact Resend.
