# Resend delivery problem notifications on your phone

> Get a High-priority push notification when Resend fails to send an email, and quieter ones when delivery is delayed or an email is suppressed.

- Company: Resend (https://www.justpush.io/recipes/resend)
- Event: `email.failed` (email.failed, email.delivery_delayed and email.suppressed)
- Tags: Email delivery, Email deliverability
- Install: https://studio.justpush.io/recipes/resend/delivery-problem
- Web page: https://www.justpush.io/recipes/resend/delivery-problem

## Setup

1. Click Install in Studio and sign in. The recipe is added to your account and you get a personal webhook URL.
2. In Resend, open Webhooks and click Add Webhook.
3. Paste your webhook URL (shown in Studio after install) as the endpoint URL and select the email.failed, email.delivery_delayed and email.suppressed events.
4. Save the webhook.
5. When you add the webhook in Resend (**Webhooks → Add Webhook**), select any of:
6. **email.failed**: Resend couldn't send the email at all, for example because you reached your daily quota, the API key or domain has a problem, or the recipient is invalid. High priority.
7. **email.delivery_delayed**: the receiving server temporarily refused the email (full inbox, transient error). Resend keeps retrying. Normal priority; leave it unticked if you send a lot and it gets noisy.
8. **email.suppressed**: Resend didn't send because the address is on a suppression list after an earlier bounce or complaint. Low priority.
9. Resend signs every delivery with Svix headers (`svix-id`, `svix-timestamp`, `svix-signature`), but JustPush doesn't check that signature yet, so keep your endpoint URL private.

## Code

Studio calls `handleRequest(request)` with the incoming webhook (`{ method, headers, body, query, raw }`) and sends the message object it returns. Returning `null` sends nothing.

```js
// Resend → email.failed / email.delivery_delayed / email.suppressed
// Fires when Resend can't send an email (quota, API key, domain problems), when delivery is
// delayed by the receiving server, or when Resend suppresses an email to a blocked address.
// Docs: https://resend.com/docs/webhooks/emails/failed

// Each event, how to label it, and how loud to be.
const EVENTS = {
    "email.failed": { title: "❌ Email failed to send", priority: 1 }, // High: sending itself is broken
    "email.delivery_delayed": { title: "⏳ Email delivery delayed", priority: 0 }, // Resend keeps retrying
    "email.suppressed": { title: "🚫 Email suppressed", priority: -1 }, // expected: protects your reputation
}

// Suppression types worth spelling out.
const SUPPRESSIONS = {
    OnAccountSuppressionList: "address is on your suppression list",
}

// Look a key up without tripping over inherited names like "toString".
function pick(table, key, fallback) {
    return Object.prototype.hasOwnProperty.call(table, key) ? table[key] : fallback
}

// Only link to real http(s) URLs.
function link(url, cta) {
    return typeof url === "string" && /^https?:\/\//.test(url) ? [{ cta, url }] : []
}

// A trimmed string, or null.
function text(value) {
    return typeof value === "string" && value.trim() ? value.trim() : null
}

// ["a@example.com", "b@example.com"] → "a@example.com +1 more"
function recipients(to) {
    const list = (Array.isArray(to) ? to : [to]).map(text).filter(Boolean)
    if (!list.length) return null
    return list[0] + (list.length > 1 ? ` +${list.length - 1} more` : "")
}

// "reached_daily_quota" → "reached daily quota"
function humanise(value) {
    const s = text(value)
    return s ? s.replace(/_/g, " ") : null
}

function handleRequest(request) {
    const body = request.body && typeof request.body === "object" ? request.body : {}
    const data = body.data && typeof body.data === "object" ? body.data : null
    const event = data ? pick(EVENTS, body.type, null) : null

    // Guard: only delivery problems, so other Resend events on this integration stay quiet.
    if (!event) {
        console.log(`Ignoring Resend "${body.type ?? "unknown"}" event`)
        return null
    }

    const subject = text(data.subject)
    let reason = null
    if (body.type === "email.failed") reason = humanise(data.failed?.reason)
    if (body.type === "email.suppressed") {
        reason = pick(SUPPRESSIONS, data.suppressed?.type, null) ?? "address is suppressed"
    }

    return {
        title: event.title,
        message: [recipients(data.to) ?? "A recipient", subject ? `"${subject}"` : null, reason]
            .filter(Boolean)
            .join(" · "),
        topic: "Resend",
        priority: event.priority,
        // The dashboard URL pattern isn't in Resend's webhook docs; it's where the email opens in the app.
        buttons: text(data.email_id)
            ? link(`https://resend.com/emails/${encodeURIComponent(data.email_id)}`, "View email")
            : [],
    }
}
```

## Resend email.failed webhook payload (sample)

```json
{
  "data": {
    "to": [
      "sam@example.com"
    ],
    "from": "Acme <hello@example.com>",
    "tags": {
      "category": "invoice"
    },
    "failed": {
      "reason": "reached_daily_quota"
    },
    "subject": "Your invoice for September",
    "email_id": "56761188-7520-42d8-8898-ff6fc54ce618",
    "created_at": "2026-09-29T08:41:11.894Z",
    "message_id": "<111-222-333@email.example.com>"
  },
  "type": "email.failed",
  "created_at": "2026-09-29T08:41:12.126Z"
}
```

## FAQ

### Does this work on iPhone and Android?

Yes. Install the JustPush app from the App Store or Google Play and sign in. Every phone signed in to your account gets the notification.

### Do I need to write code?

No. Install the recipe in Studio, paste your webhook URL into Resend and you are done. The code is there if you want to change the text, the sound or the buttons.

### Can I change what the notification says?

Yes. After install the recipe's code is yours. Edit it in Studio and test it against the sample payload before you save.

### What does it cost?

JustPush is free for 30 days. After that it's $19.99 a year, or $39.99 once. There is no extra charge for recipes.
