mailgun-inbound-email 1.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +811 -83
- package/index.js +339 -139
- package/package.json +18 -13
- package/SETUP.md +0 -62
package/README.md
CHANGED
|
@@ -1,110 +1,339 @@
|
|
|
1
1
|
# mailgun-inbound-email
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A **production-ready** utility package for manual processing of Mailgun webhooks. Supports both **inbound email webhooks** and **event webhooks** (delivered, opened, clicked, bounced, etc.). Full manual control - you handle everything from webhook setup to data processing.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 🚀 Quick Start
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install mailgun-inbound-email
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
> ⚠️ **REQUIRED**: Set `MAILGUN_WEBHOOK_SIGNING_KEY` environment variable before using webhooks. See [Security](#-security) section for details.
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
const express = require('express');
|
|
15
|
+
const multer = require('multer');
|
|
16
|
+
const { processEmailData, verifyRequestSignature } = require('mailgun-inbound-email');
|
|
17
|
+
|
|
18
|
+
const app = express();
|
|
19
|
+
const upload = multer({ storage: multer.memoryStorage() });
|
|
15
20
|
|
|
16
|
-
|
|
21
|
+
app.post('/webhook/inbound',
|
|
22
|
+
express.urlencoded({ extended: true }),
|
|
23
|
+
upload.any(),
|
|
24
|
+
(req, res) => {
|
|
25
|
+
try {
|
|
26
|
+
// Verify signature automatically (only need signing key)
|
|
27
|
+
if (!verifyRequestSignature(req, process.env.MAILGUN_WEBHOOK_SIGNING_KEY)) {
|
|
28
|
+
return res.status(401).json({ error: 'Invalid signature' });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Process email data
|
|
32
|
+
const { emailData } = processEmailData(req);
|
|
33
|
+
|
|
34
|
+
// Manual processing - you have full control
|
|
35
|
+
console.log('Email from:', emailData.from);
|
|
36
|
+
console.log('Subject:', emailData.subject);
|
|
37
|
+
|
|
38
|
+
// Your custom logic here
|
|
39
|
+
// - Save to database
|
|
40
|
+
// - Process attachments
|
|
41
|
+
// - Send notifications
|
|
42
|
+
// - etc.
|
|
43
|
+
|
|
44
|
+
res.status(200).json({ received: true });
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error('Error:', error);
|
|
47
|
+
res.status(200).json({ received: true }); // Always return 200 to Mailgun
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
app.listen(3000);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**That's it!** Just configure your Mailgun webhook URL to point to `https://yourdomain.com/webhook/inbound`
|
|
56
|
+
|
|
57
|
+
> ⚠️ **IMPORTANT**: Make sure to set `MAILGUN_WEBHOOK_SIGNING_KEY` environment variable before running the server. Without it, webhook signature verification will fail.
|
|
58
|
+
|
|
59
|
+
> 📖 **Need help setting up the webhook?** See the detailed guide: [Setting Up Mailgun Inbound Webhook](#-setting-up-mailgun-inbound-webhook)
|
|
60
|
+
|
|
61
|
+
### Event Webhooks Quick Start
|
|
62
|
+
|
|
63
|
+
For handling Mailgun event webhooks (delivered, opened, clicked, bounced, etc.):
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
const express = require('express');
|
|
67
|
+
const { mailgunWebhook } = require('mailgun-inbound-email');
|
|
68
|
+
|
|
69
|
+
const app = express();
|
|
70
|
+
|
|
71
|
+
app.post('/webhook/mailgun-events', express.json(), async (req, res) => {
|
|
72
|
+
const eventData = await mailgunWebhook(req, res);
|
|
73
|
+
|
|
74
|
+
// Save event data manually
|
|
75
|
+
if (eventData && eventData.received && eventData.event) {
|
|
76
|
+
await db.events.create(eventData);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
app.listen(3000);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**That's it!** Configure your Mailgun event webhook URL in Mailgun Dashboard → Settings → Webhooks → Add webhook → Select events → Enter URL: `https://yourdomain.com/webhook/mailgun-events`
|
|
84
|
+
|
|
85
|
+
> ⚠️ **IMPORTANT**: Make sure to set `MAILGUN_WEBHOOK_SIGNING_KEY` environment variable before running the server. Without it, webhook signature verification will fail.
|
|
86
|
+
|
|
87
|
+
## ✨ Features
|
|
88
|
+
|
|
89
|
+
- ✅ **Full Manual Control** - You handle everything, no magic
|
|
90
|
+
- ✅ **Automatic Signature Verification** - Just provide signing key, package handles the rest
|
|
91
|
+
- ✅ **Production-ready utilities** - Battle-tested functions
|
|
92
|
+
- ✅ **Mailgun signature verification** - Secure by default
|
|
93
|
+
- ✅ **Replay attack prevention** - 15-minute timestamp window
|
|
94
|
+
- ✅ **Automatic email parsing** - Clean, structured email data
|
|
95
|
+
- ✅ **Attachment support** - Metadata + buffers for manual handling
|
|
96
|
+
- ✅ **Event webhook handler** - Production-ready handler for Mailgun event webhooks (delivered, opened, clicked, bounced, etc.)
|
|
97
|
+
- ✅ **Returns event data** - Get processed event data for manual saving to database
|
|
98
|
+
- ✅ **Structured logging** - Built-in logging with correlation IDs for tracking
|
|
99
|
+
- ✅ **Zero dependencies** - Only Node.js built-ins
|
|
100
|
+
- ✅ **Simple & lightweight** - Just utility functions
|
|
101
|
+
|
|
102
|
+
## 📦 Installation
|
|
17
103
|
|
|
18
104
|
```bash
|
|
19
105
|
npm install mailgun-inbound-email
|
|
20
106
|
```
|
|
21
107
|
|
|
22
|
-
|
|
108
|
+
> ⚠️ **REQUIRED**: After installation, you must set the `MAILGUN_WEBHOOK_SIGNING_KEY` environment variable. See [Security](#-security) section for instructions.
|
|
109
|
+
|
|
110
|
+
## 🎯 Usage
|
|
23
111
|
|
|
24
|
-
###
|
|
112
|
+
### Inbound Email Webhooks
|
|
113
|
+
|
|
114
|
+
For receiving and processing inbound emails sent to your domain.
|
|
115
|
+
|
|
116
|
+
### Basic Example
|
|
25
117
|
|
|
26
118
|
```javascript
|
|
27
119
|
const express = require('express');
|
|
28
|
-
const
|
|
120
|
+
const multer = require('multer');
|
|
121
|
+
const { processEmailData, verifyRequestSignature } = require('mailgun-inbound-email');
|
|
29
122
|
|
|
30
123
|
const app = express();
|
|
124
|
+
const upload = multer({ storage: multer.memoryStorage() });
|
|
31
125
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
126
|
+
app.post('/webhook/inbound',
|
|
127
|
+
express.urlencoded({ extended: true }),
|
|
128
|
+
upload.any(),
|
|
129
|
+
(req, res) => {
|
|
130
|
+
try {
|
|
131
|
+
// Verify signature automatically - only need signing key!
|
|
132
|
+
const signingKey = process.env.MAILGUN_WEBHOOK_SIGNING_KEY;
|
|
133
|
+
if (!verifyRequestSignature(req, signingKey)) {
|
|
134
|
+
return res.status(401).json({ error: 'Invalid Mailgun signature' });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Process the email data
|
|
138
|
+
const { emailData } = processEmailData(req);
|
|
139
|
+
|
|
140
|
+
// Validate required fields
|
|
141
|
+
if (!emailData.from || !emailData.to || emailData.to.length === 0) {
|
|
142
|
+
return res.status(200).json({
|
|
143
|
+
received: true,
|
|
144
|
+
error: 'Missing required fields'
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Manual processing - you control everything
|
|
149
|
+
console.log('Processing email:', emailData.messageId);
|
|
150
|
+
console.log('From:', emailData.from);
|
|
151
|
+
console.log('To:', emailData.to);
|
|
152
|
+
console.log('Subject:', emailData.subject);
|
|
153
|
+
console.log('Attachments:', emailData.attachmentCount);
|
|
154
|
+
|
|
155
|
+
// Your custom processing logic here
|
|
156
|
+
// Example: Save to database
|
|
157
|
+
// await db.emails.create(emailData);
|
|
158
|
+
|
|
159
|
+
// Example: Process attachments
|
|
160
|
+
// emailData.attachments.forEach(attachment => {
|
|
161
|
+
// if (attachment.buffer) {
|
|
162
|
+
// fs.writeFileSync(`./uploads/${attachment.filename}`, attachment.buffer);
|
|
163
|
+
// }
|
|
164
|
+
// });
|
|
165
|
+
|
|
166
|
+
// Always return 200 to Mailgun
|
|
167
|
+
res.status(200).json({
|
|
168
|
+
received: true,
|
|
169
|
+
messageId: emailData.messageId
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error('Error processing email:', error);
|
|
174
|
+
// Always return 200 to prevent Mailgun retries
|
|
175
|
+
res.status(200).json({ received: true });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
app.listen(3000);
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Processing Attachments
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
const { processEmailData } = require('mailgun-inbound-email');
|
|
187
|
+
const fs = require('fs');
|
|
188
|
+
|
|
189
|
+
app.post('/webhook/inbound', express.urlencoded({ extended: true }), upload.any(), (req, res) => {
|
|
190
|
+
const { emailData } = processEmailData(req);
|
|
191
|
+
|
|
192
|
+
// Process attachments manually
|
|
193
|
+
emailData.attachments.forEach(attachment => {
|
|
194
|
+
if (attachment.buffer) {
|
|
195
|
+
// Save to file system
|
|
196
|
+
fs.writeFileSync(`./uploads/${attachment.filename}`, attachment.buffer);
|
|
197
|
+
|
|
198
|
+
// Or upload to S3, process image, etc.
|
|
199
|
+
// await s3.upload({
|
|
200
|
+
// Key: attachment.filename,
|
|
201
|
+
// Body: attachment.buffer,
|
|
202
|
+
// ContentType: attachment.mimetype,
|
|
203
|
+
// }).promise();
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
res.status(200).json({ received: true });
|
|
42
208
|
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Async Processing
|
|
43
212
|
|
|
44
|
-
|
|
213
|
+
```javascript
|
|
214
|
+
app.post('/webhook/inbound', express.urlencoded({ extended: true }), upload.any(), async (req, res) => {
|
|
215
|
+
try {
|
|
216
|
+
const { emailData } = processEmailData(req);
|
|
217
|
+
|
|
218
|
+
// Async operations
|
|
219
|
+
await db.emails.create(emailData);
|
|
220
|
+
await notifyTeam(emailData);
|
|
221
|
+
await processAttachments(emailData);
|
|
222
|
+
|
|
223
|
+
res.status(200).json({ received: true });
|
|
224
|
+
} catch (error) {
|
|
225
|
+
console.error('Error:', error);
|
|
226
|
+
res.status(200).json({ received: true });
|
|
227
|
+
}
|
|
228
|
+
});
|
|
45
229
|
```
|
|
46
230
|
|
|
47
|
-
###
|
|
231
|
+
### Event Webhooks (delivered, opened, clicked, bounced, etc.)
|
|
232
|
+
|
|
233
|
+
For handling Mailgun event webhooks that track email delivery, opens, clicks, and other events.
|
|
234
|
+
|
|
235
|
+
#### Simple Example
|
|
48
236
|
|
|
49
237
|
```javascript
|
|
50
238
|
const express = require('express');
|
|
51
|
-
const {
|
|
239
|
+
const { mailgunWebhook } = require('mailgun-inbound-email');
|
|
52
240
|
|
|
53
241
|
const app = express();
|
|
54
242
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
243
|
+
// Example database
|
|
244
|
+
const db = {
|
|
245
|
+
events: {
|
|
246
|
+
async create(eventData) {
|
|
247
|
+
// Save event to your database
|
|
248
|
+
console.log('Saving event:', eventData.event);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
app.post('/webhook/mailgun-events', express.json(), async (req, res) => {
|
|
254
|
+
// Call mailgunWebhook - it handles signature verification and returns event data
|
|
255
|
+
const eventData = await mailgunWebhook(req, res);
|
|
256
|
+
|
|
257
|
+
// Save event data manually if event was successfully processed
|
|
258
|
+
if (eventData && eventData.received && eventData.event) {
|
|
259
|
+
try {
|
|
260
|
+
await db.events.create(eventData);
|
|
261
|
+
console.log('✅ Event saved successfully');
|
|
262
|
+
} catch (error) {
|
|
263
|
+
console.error('❌ Error saving event:', error);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
61
266
|
});
|
|
62
267
|
|
|
63
|
-
app.
|
|
268
|
+
app.listen(3000);
|
|
64
269
|
```
|
|
65
270
|
|
|
66
|
-
|
|
271
|
+
#### Advanced Example with Event Handling
|
|
67
272
|
|
|
68
273
|
```javascript
|
|
69
274
|
const express = require('express');
|
|
70
|
-
const {
|
|
71
|
-
const multer = require('multer');
|
|
275
|
+
const { mailgunWebhook } = require('mailgun-inbound-email');
|
|
72
276
|
|
|
73
|
-
const upload = multer({ storage: multer.memoryStorage() });
|
|
74
277
|
const app = express();
|
|
75
278
|
|
|
76
|
-
app.post('/
|
|
77
|
-
const
|
|
279
|
+
app.post('/webhook/mailgun-events', express.json(), async (req, res) => {
|
|
280
|
+
const eventData = await mailgunWebhook(req, res);
|
|
78
281
|
|
|
79
|
-
if (
|
|
80
|
-
|
|
282
|
+
if (eventData && eventData.received && eventData.event) {
|
|
283
|
+
// Handle different event types
|
|
284
|
+
switch (eventData.event) {
|
|
285
|
+
case 'delivered':
|
|
286
|
+
await updateEmailStatus(eventData.messageId, 'delivered');
|
|
287
|
+
break;
|
|
288
|
+
case 'opened':
|
|
289
|
+
await trackEmailOpen(eventData.messageId, eventData.recipient);
|
|
290
|
+
break;
|
|
291
|
+
case 'clicked':
|
|
292
|
+
await trackLinkClick(eventData.messageId, eventData.url);
|
|
293
|
+
break;
|
|
294
|
+
case 'bounced':
|
|
295
|
+
await markRecipientAsBounced(eventData.recipient, eventData.reason);
|
|
296
|
+
break;
|
|
297
|
+
case 'complained':
|
|
298
|
+
await markRecipientAsComplained(eventData.recipient);
|
|
299
|
+
break;
|
|
300
|
+
case 'failed':
|
|
301
|
+
await handleEmailFailure(eventData);
|
|
302
|
+
break;
|
|
303
|
+
case 'unsubscribed':
|
|
304
|
+
await unsubscribeRecipient(eventData.recipient);
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Save event to database
|
|
309
|
+
await db.events.create(eventData);
|
|
81
310
|
}
|
|
82
|
-
|
|
83
|
-
// Your custom logic here
|
|
84
|
-
console.log(emailData);
|
|
85
|
-
res.status(200).json({ received: true });
|
|
86
311
|
});
|
|
312
|
+
|
|
313
|
+
app.listen(3000);
|
|
87
314
|
```
|
|
88
315
|
|
|
89
|
-
## Email Data Structure
|
|
316
|
+
## 📧 Email Data Structure
|
|
90
317
|
|
|
91
|
-
The `emailData` object contains:
|
|
318
|
+
The `emailData` object contains all parsed email information:
|
|
92
319
|
|
|
93
320
|
```javascript
|
|
94
321
|
{
|
|
95
|
-
messageId: "string",
|
|
96
|
-
from: "sender@example.com",
|
|
97
|
-
to: ["recipient@example.com"],
|
|
98
|
-
cc: ["cc@example.com"],
|
|
99
|
-
subject: "Email Subject",
|
|
100
|
-
text: "Plain text body",
|
|
101
|
-
html: "<html>...</html>",
|
|
102
|
-
headers: {
|
|
322
|
+
messageId: "string", // Cleaned message ID (without angle brackets)
|
|
323
|
+
from: "sender@example.com", // Sender email address (extracted from "Name <email>")
|
|
324
|
+
to: ["recipient@example.com"], // Array of recipient emails
|
|
325
|
+
cc: ["cc@example.com"], // Array of CC emails
|
|
326
|
+
subject: "Email Subject", // Email subject line
|
|
327
|
+
text: "Plain text body", // Plain text body content
|
|
328
|
+
html: "<html>...</html>", // HTML body content
|
|
329
|
+
headers: { // Parsed headers object
|
|
103
330
|
"Message-ID": "...",
|
|
104
331
|
"From": "...",
|
|
105
|
-
|
|
332
|
+
"To": "...",
|
|
333
|
+
"Subject": "...",
|
|
334
|
+
// ... all other email headers
|
|
106
335
|
},
|
|
107
|
-
attachments: [
|
|
336
|
+
attachments: [ // Attachment metadata + buffers
|
|
108
337
|
{
|
|
109
338
|
filename: "document.pdf",
|
|
110
339
|
originalname: "document.pdf",
|
|
@@ -112,50 +341,549 @@ The `emailData` object contains:
|
|
|
112
341
|
size: 12345,
|
|
113
342
|
extension: "pdf",
|
|
114
343
|
encoding: "base64",
|
|
115
|
-
fieldname: "attachment-1"
|
|
344
|
+
fieldname: "attachment-1",
|
|
345
|
+
buffer: Buffer, // File buffer for manual processing
|
|
116
346
|
}
|
|
117
347
|
],
|
|
118
|
-
attachmentCount: 1,
|
|
119
|
-
receivedAt: "2024-01-01T00:00:00.000Z",
|
|
120
|
-
timestamp: "2024-01-01T00:00:00.000Z"
|
|
348
|
+
attachmentCount: 1, // Number of attachments
|
|
349
|
+
receivedAt: "2024-01-01T00:00:00.000Z", // ISO timestamp when received
|
|
350
|
+
timestamp: "2024-01-01T00:00:00.000Z" // ISO timestamp (same as receivedAt)
|
|
121
351
|
}
|
|
122
352
|
```
|
|
123
353
|
|
|
124
|
-
##
|
|
354
|
+
## 🛠️ API Reference
|
|
355
|
+
|
|
356
|
+
### `processEmailData(req)`
|
|
357
|
+
|
|
358
|
+
Process raw Express request and return structured email data.
|
|
359
|
+
|
|
360
|
+
**Parameters:**
|
|
361
|
+
- `req` (Object): Express request object with `body` and `files` properties
|
|
362
|
+
|
|
363
|
+
**Returns:**
|
|
364
|
+
- `Object`: `{ emailData, token, timestamp, signature }`
|
|
365
|
+
|
|
366
|
+
**Throws:**
|
|
367
|
+
- `Error`: If request body is invalid
|
|
368
|
+
|
|
369
|
+
**Example:**
|
|
370
|
+
```javascript
|
|
371
|
+
const { emailData, token, timestamp, signature } = processEmailData(req);
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### `verifyRequestSignature(req, signingKey)`
|
|
125
375
|
|
|
126
|
-
|
|
376
|
+
Verify Mailgun webhook signature automatically from request. This is the **recommended** method as it automatically extracts token, timestamp, and signature from the request.
|
|
127
377
|
|
|
378
|
+
**Parameters:**
|
|
379
|
+
- `req` (Object): Express request object with body
|
|
128
380
|
- `signingKey` (string, optional): Mailgun webhook signing key. Defaults to `process.env.MAILGUN_WEBHOOK_SIGNING_KEY`
|
|
129
|
-
- `onEmailReceived` (function, optional): Callback function called when email is received. Receives `emailData` as parameter
|
|
130
|
-
- `path` (string, optional): Route path. Defaults to `'/inbound'`
|
|
131
|
-
- `requireSignature` (boolean, optional): Whether to require signature verification. Defaults to `true`
|
|
132
381
|
|
|
133
|
-
|
|
382
|
+
**Returns:**
|
|
383
|
+
- `boolean`: `true` if signature is valid
|
|
384
|
+
|
|
385
|
+
**Example:**
|
|
386
|
+
```javascript
|
|
387
|
+
const { verifyRequestSignature } = require('mailgun-inbound-email');
|
|
388
|
+
|
|
389
|
+
// Simple usage - automatically extracts token, timestamp, signature from req.body
|
|
390
|
+
// Uses MAILGUN_WEBHOOK_SIGNING_KEY from environment automatically
|
|
391
|
+
if (!verifyRequestSignature(req)) {
|
|
392
|
+
return res.status(401).json({ error: 'Invalid signature' });
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Or explicitly pass signing key
|
|
396
|
+
if (!verifyRequestSignature(req, process.env.MAILGUN_WEBHOOK_SIGNING_KEY)) {
|
|
397
|
+
return res.status(401).json({ error: 'Invalid signature' });
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### `verifyMailgunSignature(token, timestamp, signature, signingKey)`
|
|
402
|
+
|
|
403
|
+
Verify Mailgun webhook signature manually (advanced usage). Use `verifyRequestSignature()` instead for simpler usage.
|
|
404
|
+
|
|
405
|
+
**Parameters:**
|
|
406
|
+
- `token` (string): Mailgun token from request
|
|
407
|
+
- `timestamp` (string): Request timestamp
|
|
408
|
+
- `signature` (string): Mailgun signature
|
|
409
|
+
- `signingKey` (string): Your Mailgun webhook signing key
|
|
410
|
+
|
|
411
|
+
**Returns:**
|
|
412
|
+
- `boolean`: `true` if signature is valid
|
|
413
|
+
|
|
414
|
+
**Example:**
|
|
415
|
+
```javascript
|
|
416
|
+
// Advanced usage - manually extract and verify
|
|
417
|
+
const { token, timestamp, signature } = req.body;
|
|
418
|
+
const isValid = verifyMailgunSignature(token, timestamp, signature, signingKey);
|
|
419
|
+
if (!isValid) {
|
|
420
|
+
return res.status(401).json({ error: 'Invalid signature' });
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### `mailgunWebhook(req, res, signingKey)`
|
|
134
425
|
|
|
426
|
+
Production-ready handler for Mailgun event webhooks (delivered, opened, clicked, bounced, complained, failed, unsubscribed, stored, etc.). Handles signature verification, event parsing, and returns processed event data for manual saving.
|
|
427
|
+
|
|
428
|
+
**Parameters:**
|
|
429
|
+
- `req` (Object): Express request object
|
|
430
|
+
- `res` (Object): Express response object
|
|
135
431
|
- `signingKey` (string, optional): Mailgun webhook signing key. Defaults to `process.env.MAILGUN_WEBHOOK_SIGNING_KEY`
|
|
136
|
-
- `onEmailReceived` (function, optional): Callback function called when email is received. Receives `emailData` as parameter
|
|
137
|
-
- `requireSignature` (boolean, optional): Whether to require signature verification. Defaults to `true`
|
|
138
432
|
|
|
139
|
-
|
|
433
|
+
**Returns:**
|
|
434
|
+
- `Promise<Object|null>`: Returns processed event data if successful, `null` if error or invalid request
|
|
435
|
+
|
|
436
|
+
**Example:**
|
|
437
|
+
```javascript
|
|
438
|
+
const { mailgunWebhook } = require('mailgun-inbound-email');
|
|
439
|
+
|
|
440
|
+
app.post('/webhook/mailgun-events', express.json(), async (req, res) => {
|
|
441
|
+
const eventData = await mailgunWebhook(req, res);
|
|
442
|
+
|
|
443
|
+
// Save event data manually if webhook was successful
|
|
444
|
+
if (eventData && eventData.received && eventData.event) {
|
|
445
|
+
await db.events.create(eventData);
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
**Event Data Structure:**
|
|
451
|
+
```javascript
|
|
452
|
+
{
|
|
453
|
+
received: true,
|
|
454
|
+
event: "delivered" | "opened" | "clicked" | "bounced" | "complained" | "failed" | "unsubscribed" | "stored" | "unknown",
|
|
455
|
+
eventId: "string", // Unique event ID (for idempotency)
|
|
456
|
+
recipient: "user@example.com", // Email recipient
|
|
457
|
+
messageId: "string", // Email message ID
|
|
458
|
+
timestamp: "2024-01-01T00:00:00.000Z", // ISO timestamp
|
|
459
|
+
domain: "example.com", // Mailgun domain
|
|
460
|
+
correlationId: "string", // Request correlation ID for tracking
|
|
461
|
+
processedAt: "2024-01-01T00:00:00.000Z", // When webhook was processed
|
|
462
|
+
status: "delivered" | "opened" | "clicked" | "bounced" | "complained" | "failed" | "unsubscribed" | "stored" | "unknown",
|
|
463
|
+
|
|
464
|
+
// Event-specific fields:
|
|
465
|
+
url: "string", // For 'clicked' events
|
|
466
|
+
reason: "string", // For 'bounced'/'failed' events
|
|
467
|
+
deliveryStatus: { // For 'delivered'/'bounced'/'failed' events
|
|
468
|
+
code: number,
|
|
469
|
+
message: string,
|
|
470
|
+
description: string,
|
|
471
|
+
tls: boolean,
|
|
472
|
+
certificateVerified: boolean,
|
|
473
|
+
attemptNo: number,
|
|
474
|
+
sessionSeconds: number,
|
|
475
|
+
},
|
|
476
|
+
clientInfo: { // For 'opened'/'clicked' events
|
|
477
|
+
clientName: string,
|
|
478
|
+
clientType: string,
|
|
479
|
+
deviceType: string,
|
|
480
|
+
userAgent: string,
|
|
481
|
+
},
|
|
482
|
+
geolocation: { // For 'opened'/'clicked' events
|
|
483
|
+
country: string,
|
|
484
|
+
region: string,
|
|
485
|
+
city: string,
|
|
486
|
+
},
|
|
487
|
+
severity: "permanent" | "temporary", // For 'bounced' events
|
|
488
|
+
deliveredAt: "ISO string", // For 'delivered' events
|
|
489
|
+
openedAt: "ISO string", // For 'opened' events
|
|
490
|
+
clickedAt: "ISO string", // For 'clicked' events
|
|
491
|
+
bouncedAt: "ISO string", // For 'bounced' events
|
|
492
|
+
complainedAt: "ISO string", // For 'complained' events
|
|
493
|
+
failedAt: "ISO string", // For 'failed' events
|
|
494
|
+
unsubscribedAt: "ISO string", // For 'unsubscribed' events
|
|
495
|
+
storedAt: "ISO string", // For 'stored' events
|
|
496
|
+
fullEventData: {}, // For 'unknown' events - contains raw event data
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Utility Functions
|
|
501
|
+
|
|
502
|
+
| Function | Description |
|
|
503
|
+
|----------|-------------|
|
|
504
|
+
| `extractEmail(value)` | Extract email from "Name <email@domain.com>" format |
|
|
505
|
+
| `extractEmails(value)` | Extract multiple emails from comma-separated string |
|
|
506
|
+
| `cleanMessageId(value)` | Remove angle brackets from message ID |
|
|
507
|
+
| `parseHeaders(headers)` | Safely parse email headers array to object |
|
|
508
|
+
|
|
509
|
+
## 🔐 Security
|
|
510
|
+
|
|
511
|
+
### Required Environment Variable
|
|
512
|
+
|
|
513
|
+
> ⚠️ **REQUIRED**: `MAILGUN_WEBHOOK_SIGNING_KEY` must be set for webhook signature verification to work.
|
|
514
|
+
|
|
515
|
+
- **`MAILGUN_WEBHOOK_SIGNING_KEY`** (REQUIRED): Your Mailgun webhook signing key (found in Mailgun dashboard → Settings → Webhooks)
|
|
516
|
+
- This is **required** for both inbound email webhooks and event webhooks
|
|
517
|
+
- Without this key, all webhook requests will be rejected with 401 Unauthorized
|
|
518
|
+
- Get your key from: Mailgun Dashboard → Settings → Webhooks → Webhook Signing Key
|
|
519
|
+
|
|
520
|
+
### Security Features
|
|
521
|
+
|
|
522
|
+
- ✅ **Signature Verification**: Validates all webhook requests using HMAC SHA-256
|
|
523
|
+
- ✅ **Replay Attack Prevention**: Rejects requests older than 15 minutes
|
|
524
|
+
- ✅ **Timing-Safe Comparison**: Uses `crypto.timingSafeEqual` to prevent timing attacks
|
|
525
|
+
- ✅ **Input Validation**: Validates all required fields before processing
|
|
526
|
+
|
|
527
|
+
### Getting Your Signing Key
|
|
528
|
+
|
|
529
|
+
1. Log in to [Mailgun Dashboard](https://app.mailgun.com)
|
|
530
|
+
2. Go to **Settings** → **Webhooks**
|
|
531
|
+
3. Copy your **Webhook Signing Key**
|
|
532
|
+
4. Set it as environment variable: `export MAILGUN_WEBHOOK_SIGNING_KEY=your-key-here`
|
|
533
|
+
|
|
534
|
+
## 📝 Setting Up Mailgun Inbound Webhook
|
|
535
|
+
|
|
536
|
+
### Step 1: Install Package and Dependencies
|
|
537
|
+
|
|
538
|
+
```bash
|
|
539
|
+
# Install the package
|
|
540
|
+
npm install mailgun-inbound-email
|
|
541
|
+
|
|
542
|
+
# Install required dependencies
|
|
543
|
+
npm install express multer
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
### Step 2: Set Up Environment Variable (REQUIRED)
|
|
547
|
+
|
|
548
|
+
> ⚠️ **REQUIRED**: You must set `MAILGUN_WEBHOOK_SIGNING_KEY` before setting up your server.
|
|
140
549
|
|
|
141
|
-
|
|
550
|
+
1. Get your webhook signing key from Mailgun Dashboard → Settings → Webhooks
|
|
551
|
+
2. Set it as an environment variable:
|
|
142
552
|
|
|
143
|
-
|
|
553
|
+
```bash
|
|
554
|
+
export MAILGUN_WEBHOOK_SIGNING_KEY=your-signing-key-here
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
Or add to your `.env` file:
|
|
558
|
+
```
|
|
559
|
+
MAILGUN_WEBHOOK_SIGNING_KEY=your-signing-key-here
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### Step 3: Set Up Your Express Server
|
|
563
|
+
|
|
564
|
+
Set up your Express server with the webhook endpoint (see examples above).
|
|
565
|
+
|
|
566
|
+
### Step 4: Configure Mailgun Inbound Route (Dashboard Method)
|
|
567
|
+
|
|
568
|
+
Follow these steps to configure the inbound webhook URL in Mailgun Dashboard:
|
|
569
|
+
|
|
570
|
+
#### Option A: Using Mailgun Dashboard (Recommended for beginners)
|
|
571
|
+
|
|
572
|
+
1. **Log in to Mailgun Dashboard**
|
|
573
|
+
- Go to [https://app.mailgun.com](https://app.mailgun.com)
|
|
574
|
+
- Log in with your Mailgun account
|
|
575
|
+
|
|
576
|
+
2. **Navigate to Your Domain**
|
|
577
|
+
- Click on **Sending** in the left sidebar
|
|
578
|
+
- Click on **Domains**
|
|
579
|
+
- Select your verified domain (or add a new domain if needed)
|
|
144
580
|
|
|
145
|
-
|
|
581
|
+
3. **Go to Receiving Settings**
|
|
582
|
+
- In your domain settings, click on the **Receiving** tab
|
|
583
|
+
- You'll see options for handling inbound emails
|
|
146
584
|
|
|
147
|
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
150
|
-
- `extractEmails(value)`: Extract multiple emails from comma-separated string
|
|
151
|
-
- `cleanMessageId(value)`: Remove angle brackets from message ID
|
|
152
|
-
- `parseHeaders(headers)`: Safely parse email headers
|
|
585
|
+
4. **Create Inbound Route**
|
|
586
|
+
- Click on **Routes** (or **Add Route**)
|
|
587
|
+
- Click **Create Route** button
|
|
153
588
|
|
|
154
|
-
|
|
589
|
+
5. **Configure Route Settings**
|
|
590
|
+
- **Route Description**: Give it a name like "Inbound Email Webhook"
|
|
591
|
+
- **Filter Expression**:
|
|
592
|
+
- For all emails: Select `catch_all()` or leave default
|
|
593
|
+
- For specific emails: Use `match_recipient("your-email@yourdomain.com")`
|
|
594
|
+
- **Actions**:
|
|
595
|
+
- Select **Forward** or **Store and notify**
|
|
596
|
+
- Enter your webhook URL: `https://yourdomain.com/webhook/inbound`
|
|
597
|
+
- **Important**: Must use HTTPS (Mailgun requires it)
|
|
155
598
|
|
|
156
|
-
|
|
599
|
+
6. **Save the Route**
|
|
600
|
+
- Click **Create Route** or **Save**
|
|
601
|
+
- Your route is now active
|
|
157
602
|
|
|
158
|
-
|
|
603
|
+
#### Option B: Using Mailgun API (Recommended for automation)
|
|
604
|
+
|
|
605
|
+
You can also create routes programmatically using the Mailgun API:
|
|
606
|
+
|
|
607
|
+
```bash
|
|
608
|
+
curl -X POST "https://api.mailgun.net/v3/routes" \
|
|
609
|
+
-u "api:YOUR_API_KEY" \
|
|
610
|
+
-F "priority=0" \
|
|
611
|
+
-F "description=Inbound Email Webhook" \
|
|
612
|
+
-F "expression=catch_all()" \
|
|
613
|
+
-F "action=forward('https://yourdomain.com/webhook/inbound')"
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
Or using Node.js:
|
|
617
|
+
|
|
618
|
+
```javascript
|
|
619
|
+
const formData = require('form-data');
|
|
620
|
+
const Mailgun = require('mailgun.js');
|
|
621
|
+
const mailgun = new Mailgun(formData);
|
|
622
|
+
|
|
623
|
+
const mg = mailgun.client({
|
|
624
|
+
username: 'api',
|
|
625
|
+
key: process.env.MAILGUN_API_KEY
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// Create inbound route
|
|
629
|
+
mg.routes.create({
|
|
630
|
+
priority: 0,
|
|
631
|
+
description: 'Inbound Email Webhook',
|
|
632
|
+
expression: 'catch_all()',
|
|
633
|
+
action: ['forward("https://yourdomain.com/webhook/inbound")']
|
|
634
|
+
})
|
|
635
|
+
.then(msg => console.log('Route created:', msg))
|
|
636
|
+
.catch(err => console.error('Error:', err));
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
> **Note**: If you already set `MAILGUN_WEBHOOK_SIGNING_KEY` in Step 2, you can skip this step.
|
|
640
|
+
|
|
641
|
+
1. **Navigate to Webhooks Settings**
|
|
642
|
+
- In Mailgun Dashboard, go to **Settings** → **Webhooks**
|
|
643
|
+
- Or go to: [https://app.mailgun.com/app/webhooks](https://app.mailgun.com/app/webhooks)
|
|
644
|
+
|
|
645
|
+
2. **Copy Your Signing Key**
|
|
646
|
+
- Find **Webhook Signing Key** section
|
|
647
|
+
- Click **Show** or **Reveal** to see your key
|
|
648
|
+
- Copy the signing key (it looks like: `key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`)
|
|
649
|
+
|
|
650
|
+
3. **Set Environment Variable** (if not already set in Step 2)
|
|
651
|
+
```bash
|
|
652
|
+
export MAILGUN_WEBHOOK_SIGNING_KEY=your-signing-key-here
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
Or add to your `.env` file:
|
|
656
|
+
```
|
|
657
|
+
MAILGUN_WEBHOOK_SIGNING_KEY=your-signing-key-here
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
### Step 5: Test Your Webhook
|
|
661
|
+
|
|
662
|
+
1. **Deploy Your Server**
|
|
663
|
+
- Make sure your Express server is running and accessible via HTTPS
|
|
664
|
+
- Your webhook URL should be publicly accessible
|
|
665
|
+
|
|
666
|
+
2. **Send a Test Email**
|
|
667
|
+
- Send an email to any address at your domain (e.g., `test@yourdomain.com`)
|
|
668
|
+
- Mailgun will forward it to your webhook URL
|
|
669
|
+
|
|
670
|
+
3. **Check Your Logs**
|
|
671
|
+
- Check your server logs to see if the webhook was received
|
|
672
|
+
- Verify the email data is being processed correctly
|
|
673
|
+
|
|
674
|
+
4. **Verify in Mailgun Dashboard**
|
|
675
|
+
- Go to **Logs** → **Webhooks** in Mailgun Dashboard
|
|
676
|
+
- You should see webhook delivery attempts and their status
|
|
677
|
+
|
|
678
|
+
### Step 6: Verify Domain DNS Settings (If Needed)
|
|
679
|
+
|
|
680
|
+
If you haven't set up your domain yet, make sure to:
|
|
681
|
+
|
|
682
|
+
1. **Add MX Records** (for receiving emails)
|
|
683
|
+
- Go to **Sending** → **Domains** → Your Domain → **DNS Records**
|
|
684
|
+
- Add MX record pointing to Mailgun:
|
|
685
|
+
- Priority: `10`
|
|
686
|
+
- Value: `mxa.mailgun.org`
|
|
687
|
+
- Add second MX record:
|
|
688
|
+
- Priority: `10`
|
|
689
|
+
- Value: `mxb.mailgun.org`
|
|
690
|
+
|
|
691
|
+
2. **Verify Domain**
|
|
692
|
+
- Mailgun will provide DNS records to verify domain ownership
|
|
693
|
+
- Add the TXT record to your domain's DNS settings
|
|
694
|
+
- Wait for DNS propagation (can take up to 48 hours)
|
|
695
|
+
|
|
696
|
+
### Troubleshooting
|
|
697
|
+
|
|
698
|
+
**Webhook not receiving emails?**
|
|
699
|
+
- ✅ Verify your webhook URL is accessible (test with curl or browser)
|
|
700
|
+
- ✅ Ensure you're using HTTPS (Mailgun requires it)
|
|
701
|
+
- ✅ Check Mailgun logs for delivery errors
|
|
702
|
+
- ✅ Verify your route is active in Mailgun Dashboard
|
|
703
|
+
- ✅ Check your server logs for incoming requests
|
|
704
|
+
|
|
705
|
+
**Signature verification failing?**
|
|
706
|
+
- ✅ **REQUIRED**: Verify `MAILGUN_WEBHOOK_SIGNING_KEY` environment variable is set (this is required!)
|
|
707
|
+
- ✅ Check that you copied the full signing key
|
|
708
|
+
- ✅ Ensure the key matches the one in Mailgun Dashboard
|
|
709
|
+
- ✅ Verify the environment variable is loaded in your application (check with `console.log(process.env.MAILGUN_WEBHOOK_SIGNING_KEY)`)
|
|
710
|
+
|
|
711
|
+
**Emails not being forwarded?**
|
|
712
|
+
- ✅ Verify MX records are set correctly
|
|
713
|
+
- ✅ Check domain verification status
|
|
714
|
+
- ✅ Ensure route filter expression matches your test email
|
|
715
|
+
- ✅ Check Mailgun logs for any errors
|
|
716
|
+
|
|
717
|
+
### Example Webhook URL Formats
|
|
718
|
+
|
|
719
|
+
- Production: `https://api.yourdomain.com/webhook/inbound`
|
|
720
|
+
- Staging: `https://staging-api.yourdomain.com/webhook/inbound`
|
|
721
|
+
- Local testing (using ngrok): `https://abc123.ngrok.io/webhook/inbound`
|
|
722
|
+
|
|
723
|
+
**Note**: For local development, use a tool like [ngrok](https://ngrok.com/) to expose your local server:
|
|
724
|
+
```bash
|
|
725
|
+
ngrok http 3000
|
|
726
|
+
# Use the HTTPS URL provided by ngrok
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
## 📝 Setting Up Mailgun Event Webhooks
|
|
730
|
+
|
|
731
|
+
Event webhooks track email events like delivered, opened, clicked, bounced, etc. These are different from inbound email webhooks.
|
|
732
|
+
|
|
733
|
+
### Step 1: Install Package and Dependencies
|
|
734
|
+
|
|
735
|
+
```bash
|
|
736
|
+
# Install the package
|
|
737
|
+
npm install mailgun-inbound-email
|
|
738
|
+
|
|
739
|
+
# Install required dependencies (only express needed for event webhooks)
|
|
740
|
+
npm install express
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
### Step 2: Set Up Environment Variable (REQUIRED)
|
|
744
|
+
|
|
745
|
+
> ⚠️ **REQUIRED**: You must set `MAILGUN_WEBHOOK_SIGNING_KEY` before setting up your server.
|
|
746
|
+
|
|
747
|
+
1. Get your webhook signing key from Mailgun Dashboard → Settings → Webhooks
|
|
748
|
+
2. Set it as an environment variable:
|
|
749
|
+
|
|
750
|
+
```bash
|
|
751
|
+
export MAILGUN_WEBHOOK_SIGNING_KEY=your-signing-key-here
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
Or add to your `.env` file:
|
|
755
|
+
```
|
|
756
|
+
MAILGUN_WEBHOOK_SIGNING_KEY=your-signing-key-here
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
### Step 3: Set Up Your Express Server
|
|
760
|
+
|
|
761
|
+
Set up your Express server with the event webhook endpoint (see examples above in the Event Webhooks section).
|
|
762
|
+
|
|
763
|
+
### Step 4: Configure Event Webhook in Mailgun Dashboard
|
|
764
|
+
|
|
765
|
+
1. **Log in to Mailgun Dashboard**
|
|
766
|
+
- Go to [https://app.mailgun.com](https://app.mailgun.com)
|
|
767
|
+
- Log in with your Mailgun account
|
|
768
|
+
|
|
769
|
+
2. **Navigate to Webhooks Settings**
|
|
770
|
+
- Click on **Settings** in the left sidebar
|
|
771
|
+
- Click on **Webhooks**
|
|
772
|
+
- Or go directly to: [https://app.mailgun.com/app/webhooks](https://app.mailgun.com/app/webhooks)
|
|
773
|
+
|
|
774
|
+
3. **Add New Webhook**
|
|
775
|
+
- Click **Add webhook** button
|
|
776
|
+
- Select the events you want to track:
|
|
777
|
+
- ✅ **Delivered** - Email successfully delivered
|
|
778
|
+
- ✅ **Opened** - Email was opened by recipient
|
|
779
|
+
- ✅ **Clicked** - Link in email was clicked
|
|
780
|
+
- ✅ **Bounced** - Email bounced (permanent or temporary)
|
|
781
|
+
- ✅ **Complained** - Recipient marked email as spam
|
|
782
|
+
- ✅ **Failed** - Email delivery failed
|
|
783
|
+
- ✅ **Unsubscribed** - Recipient unsubscribed
|
|
784
|
+
- ✅ **Stored** - Email was stored
|
|
785
|
+
|
|
786
|
+
4. **Enter Webhook URL**
|
|
787
|
+
- Enter your webhook URL: `https://yourdomain.com/webhook/mailgun-events`
|
|
788
|
+
- **Important**: Must use HTTPS (Mailgun requires it)
|
|
789
|
+
- The webhook will receive JSON payloads (not form-data like inbound emails)
|
|
790
|
+
|
|
791
|
+
5. **Save the Webhook**
|
|
792
|
+
- Click **Save** or **Add webhook**
|
|
793
|
+
- Your webhook is now active and will receive events
|
|
794
|
+
|
|
795
|
+
> **Note**: If you already set `MAILGUN_WEBHOOK_SIGNING_KEY` in Step 2, you can skip this step. The same signing key is used for both inbound and event webhooks.
|
|
796
|
+
|
|
797
|
+
1. **Navigate to Webhooks Settings**
|
|
798
|
+
- In Mailgun Dashboard, go to **Settings** → **Webhooks**
|
|
799
|
+
- Find **Webhook Signing Key** section
|
|
800
|
+
|
|
801
|
+
2. **Copy Your Signing Key**
|
|
802
|
+
- Click **Show** or **Reveal** to see your key
|
|
803
|
+
- Copy the signing key (it looks like: `key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`)
|
|
804
|
+
|
|
805
|
+
3. **Set Environment Variable** (if not already set in Step 2)
|
|
806
|
+
```bash
|
|
807
|
+
export MAILGUN_WEBHOOK_SIGNING_KEY=your-signing-key-here
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
Or add to your `.env` file:
|
|
811
|
+
```
|
|
812
|
+
MAILGUN_WEBHOOK_SIGNING_KEY=your-signing-key-here
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
### Step 5: Test Your Event Webhook
|
|
816
|
+
|
|
817
|
+
1. **Send a Test Email**
|
|
818
|
+
- Send an email using Mailgun API or dashboard
|
|
819
|
+
- The email should trigger events (delivered, opened, clicked, etc.)
|
|
820
|
+
|
|
821
|
+
2. **Check Your Logs**
|
|
822
|
+
- Check your server logs to see if events are being received
|
|
823
|
+
- Verify the event data is being processed correctly
|
|
824
|
+
|
|
825
|
+
3. **Verify in Mailgun Dashboard**
|
|
826
|
+
- Go to **Logs** → **Webhooks** in Mailgun Dashboard
|
|
827
|
+
- You should see webhook delivery attempts and their status
|
|
828
|
+
- Check that events are being sent to your webhook URL
|
|
829
|
+
|
|
830
|
+
### Troubleshooting Event Webhooks
|
|
831
|
+
|
|
832
|
+
**Events not being received?**
|
|
833
|
+
- ✅ Verify your webhook URL is accessible (test with curl or browser)
|
|
834
|
+
- ✅ Ensure you're using HTTPS (Mailgun requires it)
|
|
835
|
+
- ✅ Check that you selected the correct events in Mailgun Dashboard
|
|
836
|
+
- ✅ Verify your webhook is active in Mailgun Dashboard
|
|
837
|
+
- ✅ Check Mailgun logs for delivery errors
|
|
838
|
+
- ✅ Check your server logs for incoming requests
|
|
839
|
+
|
|
840
|
+
**Event data not saving?**
|
|
841
|
+
- ✅ Verify `mailgunWebhook()` is returning event data
|
|
842
|
+
- ✅ Check that you're checking `eventData.received && eventData.event` before saving
|
|
843
|
+
- ✅ Ensure your database connection is working
|
|
844
|
+
- ✅ Check for errors in your event saving logic
|
|
845
|
+
|
|
846
|
+
**Signature verification failing?**
|
|
847
|
+
- ✅ **REQUIRED**: Verify `MAILGUN_WEBHOOK_SIGNING_KEY` environment variable is set (this is required!)
|
|
848
|
+
- ✅ Check that you copied the full signing key
|
|
849
|
+
- ✅ Ensure the key matches the one in Mailgun Dashboard
|
|
850
|
+
- ✅ Verify the environment variable is loaded in your application (check with `console.log(process.env.MAILGUN_WEBHOOK_SIGNING_KEY)`)
|
|
851
|
+
|
|
852
|
+
## 🎯 Production Checklist
|
|
853
|
+
|
|
854
|
+
### Inbound Email Webhooks
|
|
855
|
+
- ✅ **REQUIRED**: Set `MAILGUN_WEBHOOK_SIGNING_KEY` environment variable
|
|
856
|
+
- ✅ Use HTTPS for webhook URL (Mailgun requires it)
|
|
857
|
+
- ✅ Implement your email processing logic
|
|
858
|
+
- ✅ Handle attachments if needed (buffers are included)
|
|
859
|
+
- ✅ Set up error monitoring/logging
|
|
860
|
+
- ✅ Test webhook signature verification
|
|
861
|
+
- ✅ Always return 200 status to Mailgun (prevents retries)
|
|
862
|
+
|
|
863
|
+
### Event Webhooks (delivered, opened, clicked, etc.)
|
|
864
|
+
- ✅ **REQUIRED**: Set `MAILGUN_WEBHOOK_SIGNING_KEY` environment variable
|
|
865
|
+
- ✅ Use HTTPS for webhook URL (Mailgun requires it)
|
|
866
|
+
- ✅ Implement event data saving logic (use returned event data)
|
|
867
|
+
- ✅ Handle different event types appropriately
|
|
868
|
+
- ✅ Set up error monitoring/logging
|
|
869
|
+
- ✅ Test webhook signature verification
|
|
870
|
+
- ✅ Always return 200 status to Mailgun (prevents retries)
|
|
871
|
+
- ✅ Consider implementing idempotency checks using `eventId`
|
|
872
|
+
|
|
873
|
+
## ⚠️ Important Notes
|
|
874
|
+
|
|
875
|
+
- **Always return 200** to Mailgun (even on errors) to prevent retries
|
|
876
|
+
- **Use HTTPS** for webhook URLs (Mailgun requirement)
|
|
877
|
+
- **Full manual control** - this package only provides utilities, you handle everything
|
|
878
|
+
- **Attachments include buffers** - handle large files appropriately
|
|
879
|
+
- **Event webhooks return data** - `mailgunWebhook()` returns event data for manual saving
|
|
880
|
+
- **Zero dependencies** - only uses Node.js built-in modules
|
|
881
|
+
- **Two webhook types** - Inbound email webhooks (form-data) vs Event webhooks (JSON)
|
|
882
|
+
|
|
883
|
+
## 📄 License
|
|
159
884
|
|
|
160
885
|
MIT
|
|
161
886
|
|
|
887
|
+
## 🤝 Contributing
|
|
888
|
+
|
|
889
|
+
Contributions welcome! Please open an issue or submit a pull request.
|