SendGrid delivery problems notifications on your phone.

Get one push notification per SendGrid Event Webhook batch that summarises its bounces, blocks, drops and spam reports, and a Highest-priority one if your account is suspended.

How it works

Three steps to your first push

STEP 01

Install the recipe

One click in Studio gives you a personal webhook URL.

STEP 02

Paste your URL into SendGrid

Add it as a webhook for bounce, dropped and spam report.

STEP 03

Get a push on your phone

With the text, sound and buttons from the recipe.

Setup

How to set up the SendGrid bounce, dropped and spam report webhook

  1. Click Install in Studio and sign in. The recipe is added to your account and you get a personal webhook URL.
  2. In SendGrid, go to Settings > Mail Settings > Event Webhooks and click Create new webhook.
  3. Paste your webhook URL as the Post URL and, under Actions to be posted, tick the bounce, dropped and spam report events.https://••••••••/•••••••• your personal URL, shown after install
  4. Click Test Your Integration to send a batch of example events, then Save.
  5. Under **Actions to be posted**, tick the bounce, dropped and spam report actions. Tick deferred too only if you also add `deferred` to `?events=` (see below). You can leave Delivered, Opened, Clicked and the rest off; the recipe ignores them anyway.
  6. SendGrid sends events in **batches** (a JSON array, usually a few seconds' worth), so you get **one push per batch** that counts what went wrong, such as **🚨 1 spam report, 2 bounces**, and names the first address. Priority follows the worst kind in the batch: spam reports are High, bounces and drops are Normal, blocks and deferrals are Low.
  7. Emails that SendGrid drops because the address already bounced, unsubscribed or reported spam are skipped, since SendGrid is protecting you. Add `?all=1` to count those too.
  8. To choose what pushes, add `?events=` with a comma-separated list of `spamreport`, `bounce`, `dropped`, `blocked` and `deferred`. For example, spam reports only:
  9. ```
  10. your webhook URL?events=spamreport
  11. ```
  12. If SendGrid sends an **account status change** (suspension, deactivation or ban for compliance reasons), you get a Highest-priority push, whatever `?events=` says.
  13. **Test Your Integration** sends a batch of SendGrid's example events, which gives one push. SendGrid can sign requests (Signed Event Webhook, `X-Twilio-Email-Event-Webhook-Signature`) and use OAuth, but JustPush doesn't check either yet, so keep your endpoint URL private.
The code

What runs when the webhook arrives

Studio calls handleRequest(request) with the incoming webhook and sends the message it returns. It's yours after install; change anything.

transform.js
1// SendGrid → Event Webhook (bounces, blocks, drops, spam reports)
2// SendGrid POSTs a JSON array of events. This sends one push per batch that summarises the
3// problems in it ("1 spam report, 2 bounces") and ignores delivered, open, click and the rest.
4// Account status changes (suspension, deactivation, ban) push at Highest priority.
5// Docs: https://www.twilio.com/docs/sendgrid/for-developers/tracking-events/event
6
7// Problem kinds, loudest first, with their labels and priorities.
8const KINDS = {
9 spamreport: { one: "spam report", many: "spam reports", emoji: "🚨", priority: 1 }, // hurts your reputation
10 bounce: { one: "bounce", many: "bounces", emoji: "↩️", priority: 0 }, // permanent; worth knowing
11 dropped: { one: "dropped email", many: "dropped emails", emoji: "🚫", priority: 0 },
12 blocked: { one: "block", many: "blocks", emoji: "⛔", priority: -1 }, // temporary; may still arrive
13 deferred: { one: "deferral", many: "deferrals", emoji: "⏳", priority: -1 }, // SendGrid keeps retrying
14}
15const ORDER = ["spamreport", "bounce", "dropped", "blocked", "deferred"]
16const DEFAULT_KINDS = ["spamreport", "bounce", "dropped", "blocked"]
17
18// Drops where SendGrid is protecting you from a known-bad address; expected, so skipped by default.
19const SUPPRESSED_DROPS = ["Bounced Address", "Unsubscribed Address", "Spam Reporting Address"]
20
21// Account status changes and what they mean.
22const ACCOUNT = {
23 compliance_suspend: { title: "🚨 SendGrid account suspended", message: "Email delivery is blocked", priority: 2 },
24 compliance_deactivate: { title: "🚨 SendGrid account deactivated", message: "Queued email was deleted", priority: 2 },
25 compliance_ban: { title: "🚨 SendGrid account banned", message: "Your account has been banned", priority: 2 },
26 reactivate: { title: "✅ SendGrid account reactivated", message: "Email delivery is back on", priority: -1 },
27}
28
29// Look a key up without tripping over inherited names like "toString".
30function pick(table, key, fallback) {
31 return Object.prototype.hasOwnProperty.call(table, key) ? table[key] : fallback
32}
33
34// A trimmed string, or null.
35function text(value) {
36 return typeof value === "string" && value.trim() ? value.trim() : null
37}
38
39// Which problem kind an event is, or null.
40function kindOf(event) {
41 if (event.event === "bounce") return event.type === "blocked" ? "blocked" : "bounce"
42 return ["spamreport", "dropped", "deferred"].includes(event.event) ? event.event : null
43}
44
45// One event in words: "[email protected] · 550 unknown recipient".
46function describe(kind, event) {
47 const email = text(event.email) ?? "A recipient"
48 if (kind === "spamreport") return `${email} marked your email as spam`
49 const reason = kind === "deferred" ? text(event.response) : text(event.reason) ?? text(event.bounce_classification)
50 return `${email} · ${reason ?? KINDS[kind].one}`
51}
52
53function handleRequest(request) {
54 // SendGrid always sends an array; accept a single event object too (e.g. hand-made tests).
55 const body = request.body
56 const events = (Array.isArray(body) ? body : body && typeof body === "object" && body.event ? [body] : [])
57 .filter((e) => e && typeof e === "object" && typeof e.event === "string")
58
59 if (!events.length) {
60 console.log("Not a SendGrid Event Webhook batch — skipping")
61 return null
62 }
63
64 // ?events=spamreport,bounce limits which kinds push; ?all=1 also counts drops to suppressed addresses.
65 const query = request.query || {}
66 const all = ["1", "true", "yes"].includes(String(query.all ?? "").toLowerCase())
67 const wanted = text(query.events)
68 ? String(query.events).toLowerCase().split(",").map((s) => s.trim()).filter((s) => ORDER.includes(s))
69 : DEFAULT_KINDS
70
71 const groups = {}
72 for (const event of events) {
73 const kind = kindOf(event)
74 if (!kind || !wanted.includes(kind)) continue
75 if (kind === "dropped" && !all && SUPPRESSED_DROPS.includes(event.reason)) continue
76 if (!groups[kind]) groups[kind] = []
77 groups[kind].push(event)
78 }
79 const kinds = ORDER.filter((k) => groups[k])
80 const summary = kinds
81 .map((k) => `${groups[k].length} ${groups[k].length === 1 ? KINDS[k].one : KINDS[k].many}`)
82 .join(", ")
83
84 // An account status change outranks everything else in the batch.
85 const status = events.find((e) => e.event === "account_status_change" && pick(ACCOUNT, e.type, null))
86 if (status) {
87 const account = ACCOUNT[status.type]
88 return {
89 title: account.title,
90 message: account.message + (summary ? ` · also ${summary}` : ""),
91 topic: "SendGrid",
92 priority: account.priority, // Highest: nothing is being delivered right now
93 }
94 }
95
96 if (!kinds.length) {
97 console.log(`No problems in this batch of ${events.length} event(s) — skipping`)
98 return null
99 }
100
101 // Lead with the loudest kind's first event.
102 const lead = KINDS[kinds[0]]
103 const first = groups[kinds[0]][0]
104 const total = kinds.reduce((n, k) => n + groups[k].length, 0)
105
106 return {
107 title: `${lead.emoji} ${summary}`,
108 message: describe(kinds[0], first) + (total > 1 ? ` · +${total - 1} more` : ""),
109 topic: "SendGrid",
110 priority: lead.priority,
111 }
112}
Payload

The SendGrid bounce webhook

This is what SendGrid sends to your URL for bounce, dropped and spam report. It is a sample, trimmed to the fields recipes use.

bounce · sample.json
1[
2 {
3 "email": "[email protected]",
4 "event": "delivered",
5 "smtp-id": "<14c5d75ce93.dfd.64b469@ismtpd-555>",
6 "category": "welcome",
7 "response": "250 OK",
8 "timestamp": 1790670569,
9 "sg_event_id": "rWVYmVk90MjZJ9iohOBa3w==",
10 "sg_message_id": "14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0"
11 },
12 {
13 "email": "[email protected]",
14 "event": "spamreport",
15 "smtp-id": "<14c5d75ce94.dfd.64b469@ismtpd-555>",
16 "timestamp": 1790670570,
17 "sg_event_id": "37nvH5QBz858KGVYCM4uOA==",
18 "sg_message_id": "14c5d75ce94.dfd.64b469.filter0001.16648.5515E0B88.0"
19 },
20 {
21 "type": "bounce",
22 "email": "[email protected]",
23 "event": "bounce",
24 "reason": "550 5.1.1 unknown recipient",
25 "status": "5.1.1",
26 "smtp-id": "<14c5d75ce95.dfd.64b469@ismtpd-555>",
27 "category": "welcome",
28 "timestamp": 1790670571,
29 "sg_event_id": "6g4ZI7SA-xmRDv57GoPIPw==",
30 "sg_message_id": "14c5d75ce95.dfd.64b469.filter0001.16648.5515E0B88.0",
31 "bounce_classification": "Invalid Address"
32 },
33 {
34 "type": "bounce",
35 "email": "[email protected]",
36 "event": "bounce",
37 "reason": "550 5.2.1 mailbox disabled",
38 "status": "5.2.1",
39 "smtp-id": "<14c5d75ce96.dfd.64b469@ismtpd-555>",
40 "category": "welcome",
41 "timestamp": 1790670572,
42 "sg_event_id": "7h5ZJ8TB-ynSEw68HpQJQx==",
43 "sg_message_id": "14c5d75ce96.dfd.64b469.filter0001.16648.5515E0B88.0",
44 "bounce_classification": "Mailbox Unavailable"
45 },
46 {
47 "ip": "192.0.2.10",
48 "email": "[email protected]",
49 "event": "open",
50 "category": "welcome",
51 "timestamp": 1790670580,
52 "useragent": "Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
53 "sg_event_id": "FOTFFO0ecsBE-zxFXfs6WA==",
54 "sg_message_id": "14c5d75ce93.dfd.64b469.filter0001.16648.5515E0B88.0",
55 "sg_machine_open": false
56 }
57]
FAQ

SendGrid delivery problems notifications: questions

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 SendGrid 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.

Ready when you are

SendGrid on your phone in two minutes.

Install the recipe, paste one URL, done. Free for 30 days, no credit card required.