mailgun-inbound-email 1.0.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/README.md +161 -0
- package/SETUP.md +62 -0
- package/index.js +282 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# mailgun-inbound-email
|
|
2
|
+
|
|
3
|
+
A reusable npm package for handling Mailgun inbound email webhooks with Express.js. This package provides a clean, tested solution for receiving and processing inbound emails from Mailgun without rewriting the same code in every project.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ Mailgun webhook signature verification
|
|
8
|
+
- ✅ Replay attack prevention (15-minute window)
|
|
9
|
+
- ✅ Automatic email parsing (from, to, cc, subject, body, attachments)
|
|
10
|
+
- ✅ Header extraction and normalization
|
|
11
|
+
- ✅ Attachment metadata processing
|
|
12
|
+
- ✅ Express.js router and middleware support
|
|
13
|
+
- ✅ Customizable callback functions
|
|
14
|
+
- ✅ Error handling that prevents Mailgun retries
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install mailgun-inbound-email
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### Option 1: Using Router (Recommended)
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
const express = require('express');
|
|
28
|
+
const { createMailgunInboundRouter } = require('mailgun-inbound-email');
|
|
29
|
+
|
|
30
|
+
const app = express();
|
|
31
|
+
|
|
32
|
+
// Create router with callback
|
|
33
|
+
const mailgunRouter = createMailgunInboundRouter({
|
|
34
|
+
signingKey: process.env.MAILGUN_WEBHOOK_SIGNING_KEY, // or use env var
|
|
35
|
+
onEmailReceived: (emailData) => {
|
|
36
|
+
// Handle the email data
|
|
37
|
+
console.log('Received email:', emailData);
|
|
38
|
+
// Save to database, send notifications, etc.
|
|
39
|
+
},
|
|
40
|
+
path: '/inbound', // optional, defaults to '/inbound'
|
|
41
|
+
requireSignature: true, // optional, defaults to true
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
app.use('/email', mailgunRouter);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Option 2: Using Middleware
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
const express = require('express');
|
|
51
|
+
const { createMailgunInboundMiddleware } = require('mailgun-inbound-email');
|
|
52
|
+
|
|
53
|
+
const app = express();
|
|
54
|
+
|
|
55
|
+
const mailgunMiddleware = createMailgunInboundMiddleware({
|
|
56
|
+
signingKey: process.env.MAILGUN_WEBHOOK_SIGNING_KEY,
|
|
57
|
+
onEmailReceived: (emailData) => {
|
|
58
|
+
// Handle the email data
|
|
59
|
+
console.log('Received email:', emailData);
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
app.post('/email/inbound', ...mailgunMiddleware);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Option 3: Manual Processing
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
const express = require('express');
|
|
70
|
+
const { processEmailData, verifyMailgunSignature } = require('mailgun-inbound-email');
|
|
71
|
+
const multer = require('multer');
|
|
72
|
+
|
|
73
|
+
const upload = multer({ storage: multer.memoryStorage() });
|
|
74
|
+
const app = express();
|
|
75
|
+
|
|
76
|
+
app.post('/email/inbound', express.urlencoded({ extended: true }), upload.any(), (req, res) => {
|
|
77
|
+
const { emailData, token, timestamp, signature } = processEmailData(req);
|
|
78
|
+
|
|
79
|
+
if (!verifyMailgunSignature(token, timestamp, signature, process.env.MAILGUN_WEBHOOK_SIGNING_KEY)) {
|
|
80
|
+
return res.status(401).json({ error: 'Invalid signature' });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Your custom logic here
|
|
84
|
+
console.log(emailData);
|
|
85
|
+
res.status(200).json({ received: true });
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Email Data Structure
|
|
90
|
+
|
|
91
|
+
The `emailData` object contains:
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
{
|
|
95
|
+
messageId: "string", // Cleaned message ID
|
|
96
|
+
from: "sender@example.com", // Sender email address
|
|
97
|
+
to: ["recipient@example.com"], // Array of recipient emails
|
|
98
|
+
cc: ["cc@example.com"], // Array of CC emails
|
|
99
|
+
subject: "Email Subject", // Email subject
|
|
100
|
+
text: "Plain text body", // Plain text body
|
|
101
|
+
html: "<html>...</html>", // HTML body
|
|
102
|
+
headers: { // Parsed headers object
|
|
103
|
+
"Message-ID": "...",
|
|
104
|
+
"From": "...",
|
|
105
|
+
// ... other headers
|
|
106
|
+
},
|
|
107
|
+
attachments: [ // Attachment metadata
|
|
108
|
+
{
|
|
109
|
+
filename: "document.pdf",
|
|
110
|
+
originalname: "document.pdf",
|
|
111
|
+
mimetype: "application/pdf",
|
|
112
|
+
size: 12345,
|
|
113
|
+
extension: "pdf",
|
|
114
|
+
encoding: "base64",
|
|
115
|
+
fieldname: "attachment-1"
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
attachmentCount: 1,
|
|
119
|
+
receivedAt: "2024-01-01T00:00:00.000Z",
|
|
120
|
+
timestamp: "2024-01-01T00:00:00.000Z"
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Configuration Options
|
|
125
|
+
|
|
126
|
+
### `createMailgunInboundRouter(options)`
|
|
127
|
+
|
|
128
|
+
- `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
|
+
|
|
133
|
+
### `createMailgunInboundMiddleware(options)`
|
|
134
|
+
|
|
135
|
+
- `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
|
+
|
|
139
|
+
## Environment Variables
|
|
140
|
+
|
|
141
|
+
- `MAILGUN_WEBHOOK_SIGNING_KEY`: Your Mailgun webhook signing key (if not provided in options)
|
|
142
|
+
|
|
143
|
+
## Utilities
|
|
144
|
+
|
|
145
|
+
The package also exports utility functions:
|
|
146
|
+
|
|
147
|
+
- `processEmailData(req)`: Process raw request and return email data
|
|
148
|
+
- `verifyMailgunSignature(token, timestamp, signature, signingKey)`: Verify webhook signature
|
|
149
|
+
- `extractEmail(value)`: Extract email from "Name <email@domain.com>" format
|
|
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
|
|
153
|
+
|
|
154
|
+
## Error Handling
|
|
155
|
+
|
|
156
|
+
The package automatically handles errors and always returns `200` status to Mailgun to prevent retries. Errors are logged to the console.
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
MIT
|
|
161
|
+
|
package/SETUP.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Setup Instructions
|
|
2
|
+
|
|
3
|
+
## For Local Development
|
|
4
|
+
|
|
5
|
+
To use this package in your project before publishing to npm:
|
|
6
|
+
|
|
7
|
+
### Option 1: npm link (Recommended for development)
|
|
8
|
+
|
|
9
|
+
1. In the `mailgun-inbound-email` directory:
|
|
10
|
+
```bash
|
|
11
|
+
cd /home/ritik-ls/Desktop/mailgun-inbound-email
|
|
12
|
+
npm link
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
2. In your project directory:
|
|
16
|
+
```bash
|
|
17
|
+
cd /home/ritik-ls/Desktop/node_skeleton
|
|
18
|
+
npm link mailgun-inbound-email
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Option 2: Install from local path
|
|
22
|
+
|
|
23
|
+
In your project's `package.json`, add:
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"mailgun-inbound-email": "file:../mailgun-inbound-email"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Then run:
|
|
33
|
+
```bash
|
|
34
|
+
npm install
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Option 3: Publish to npm (for production use)
|
|
38
|
+
|
|
39
|
+
1. Update `package.json` with your details (name, author, etc.)
|
|
40
|
+
2. Login to npm:
|
|
41
|
+
```bash
|
|
42
|
+
npm login
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
3. Publish:
|
|
46
|
+
```bash
|
|
47
|
+
npm publish
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
4. Then install in your project:
|
|
51
|
+
```bash
|
|
52
|
+
npm install mailgun-inbound-email
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## After Setup
|
|
56
|
+
|
|
57
|
+
Make sure to install dependencies in the package directory:
|
|
58
|
+
```bash
|
|
59
|
+
cd /home/ritik-ls/Desktop/mailgun-inbound-email
|
|
60
|
+
npm install
|
|
61
|
+
```
|
|
62
|
+
|
package/index.js
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const crypto = require("crypto");
|
|
3
|
+
const multer = require("multer");
|
|
4
|
+
|
|
5
|
+
const upload = multer({ storage: multer.memoryStorage() });
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Verify Mailgun webhook signature
|
|
9
|
+
*/
|
|
10
|
+
function verifyMailgunSignature(token, timestamp, signature, signingKey) {
|
|
11
|
+
if (!signingKey) {
|
|
12
|
+
console.error("MAILGUN_WEBHOOK_SIGNING_KEY missing");
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const currentTime = Math.floor(Date.now() / 1000);
|
|
17
|
+
|
|
18
|
+
// Prevent replay attack (15 min)
|
|
19
|
+
if (Math.abs(currentTime - Number(timestamp)) > 900) {
|
|
20
|
+
console.error("Expired timestamp");
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const hmac = crypto
|
|
25
|
+
.createHmac("sha256", signingKey)
|
|
26
|
+
.update(timestamp + token)
|
|
27
|
+
.digest("hex");
|
|
28
|
+
|
|
29
|
+
return hmac === signature;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parse headers safely
|
|
34
|
+
*/
|
|
35
|
+
function parseHeaders(headers) {
|
|
36
|
+
if (Array.isArray(headers)) return headers;
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(headers || "[]");
|
|
40
|
+
} catch {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Extract email from "Name <email@domain.com>" or plain email
|
|
47
|
+
*/
|
|
48
|
+
function extractEmail(value = "") {
|
|
49
|
+
if (!value || typeof value !== 'string') return "";
|
|
50
|
+
const match = value.match(/<(.+?)>/);
|
|
51
|
+
return match ? match[1].trim() : value.trim();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Extract multiple emails from comma-separated string
|
|
56
|
+
*/
|
|
57
|
+
function extractEmails(value = "") {
|
|
58
|
+
if (!value || typeof value !== 'string') return [];
|
|
59
|
+
return value.split(',').map(email => extractEmail(email.trim())).filter(Boolean);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Remove angle brackets from message ID
|
|
64
|
+
*/
|
|
65
|
+
function cleanMessageId(value) {
|
|
66
|
+
if (!value || typeof value !== 'string') return null;
|
|
67
|
+
return value.replace(/^<|>$/g, '').trim() || null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Process email data from Mailgun webhook
|
|
72
|
+
*/
|
|
73
|
+
function processEmailData(req) {
|
|
74
|
+
const {
|
|
75
|
+
token,
|
|
76
|
+
timestamp,
|
|
77
|
+
signature,
|
|
78
|
+
sender,
|
|
79
|
+
from,
|
|
80
|
+
subject,
|
|
81
|
+
recipient,
|
|
82
|
+
cc,
|
|
83
|
+
"body-plain": bodyPlain,
|
|
84
|
+
"body-html": bodyHtml,
|
|
85
|
+
"stripped-text": strippedText,
|
|
86
|
+
"stripped-html": strippedHtml,
|
|
87
|
+
"message-headers": messageHeaders,
|
|
88
|
+
"attachment-count": attachmentCount,
|
|
89
|
+
} = req.body;
|
|
90
|
+
|
|
91
|
+
// Process attachments metadata (without buffer for clean JSON)
|
|
92
|
+
const processedAttachments = (req.files || []).map((file) => ({
|
|
93
|
+
filename: file.originalname || `attachment-${Date.now()}`,
|
|
94
|
+
originalname: file.originalname || null,
|
|
95
|
+
mimetype: file.mimetype || null,
|
|
96
|
+
size: file.size || 0,
|
|
97
|
+
extension: file.originalname ? file.originalname.split('.').pop() : null,
|
|
98
|
+
encoding: file.encoding || null,
|
|
99
|
+
fieldname: file.fieldname || null,
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
// Convert headers array to object for easier storage
|
|
103
|
+
const headersObj = {};
|
|
104
|
+
const parsedHeaders = parseHeaders(messageHeaders);
|
|
105
|
+
if (parsedHeaders && parsedHeaders.length > 0) {
|
|
106
|
+
parsedHeaders.forEach(header => {
|
|
107
|
+
if (Array.isArray(header) && header.length >= 2) {
|
|
108
|
+
const [key, value] = header;
|
|
109
|
+
headersObj[key] = value;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Extract CC from body or headers
|
|
115
|
+
const ccValue = cc || headersObj['Cc'] || headersObj['CC'] || ""
|
|
116
|
+
// Extract TO from body or headers (can be multiple recipients)
|
|
117
|
+
const toValue = recipient || headersObj['To'] || headersObj['TO'] || ""
|
|
118
|
+
|
|
119
|
+
const emailData = {
|
|
120
|
+
messageId: cleanMessageId(headersObj['Message-ID'] || headersObj['Message-Id'] || null),
|
|
121
|
+
from: extractEmail(sender || from),
|
|
122
|
+
to: extractEmails(toValue),
|
|
123
|
+
cc: extractEmails(ccValue),
|
|
124
|
+
subject: subject || "",
|
|
125
|
+
text: bodyPlain || strippedText || "",
|
|
126
|
+
html: bodyHtml || strippedHtml || "",
|
|
127
|
+
headers: headersObj,
|
|
128
|
+
attachments: processedAttachments,
|
|
129
|
+
attachmentCount: Number(attachmentCount || 0),
|
|
130
|
+
receivedAt: new Date().toISOString(),
|
|
131
|
+
timestamp: new Date().toISOString(),
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
emailData,
|
|
136
|
+
token,
|
|
137
|
+
timestamp,
|
|
138
|
+
signature,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Create Express router for Mailgun inbound email webhook
|
|
144
|
+
*
|
|
145
|
+
* @param {Object} options - Configuration options
|
|
146
|
+
* @param {string} options.signingKey - Mailgun webhook signing key (or use MAILGUN_WEBHOOK_SIGNING_KEY env var)
|
|
147
|
+
* @param {Function} options.onEmailReceived - Callback function called when email is received (emailData) => {}
|
|
148
|
+
* @param {string} options.path - Route path (default: '/inbound')
|
|
149
|
+
* @param {boolean} options.requireSignature - Whether to require signature verification (default: true)
|
|
150
|
+
* @returns {express.Router} Express router
|
|
151
|
+
*/
|
|
152
|
+
function createMailgunInboundRouter(options = {}) {
|
|
153
|
+
const router = express.Router();
|
|
154
|
+
const {
|
|
155
|
+
signingKey = process.env.MAILGUN_WEBHOOK_SIGNING_KEY,
|
|
156
|
+
onEmailReceived,
|
|
157
|
+
path = '/inbound',
|
|
158
|
+
requireSignature = true,
|
|
159
|
+
} = options;
|
|
160
|
+
|
|
161
|
+
router.post(path, express.urlencoded({ extended: true }), upload.any(), (req, res) => {
|
|
162
|
+
try {
|
|
163
|
+
const { emailData, token, timestamp, signature } = processEmailData(req);
|
|
164
|
+
|
|
165
|
+
// 🔐 Verify Mailgun authenticity
|
|
166
|
+
if (requireSignature && !verifyMailgunSignature(token, timestamp, signature, signingKey)) {
|
|
167
|
+
return res.status(401).json({ error: "Invalid Mailgun signature" });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Validate required fields
|
|
171
|
+
if (!emailData.from || !emailData.to || emailData.to.length === 0) {
|
|
172
|
+
console.error("Missing required email fields:", { from: emailData.from, to: emailData.to });
|
|
173
|
+
// Still return 200 to prevent Mailgun retries
|
|
174
|
+
return res.status(200).json({ received: true, error: "Missing required fields" });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Call user-provided callback if provided
|
|
178
|
+
if (onEmailReceived && typeof onEmailReceived === 'function') {
|
|
179
|
+
try {
|
|
180
|
+
onEmailReceived(emailData);
|
|
181
|
+
} catch (callbackError) {
|
|
182
|
+
console.error("Error in onEmailReceived callback:", callbackError);
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
// Default: log clean JSON data ready to save
|
|
186
|
+
console.log(JSON.stringify(emailData, null, 2));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
res.status(200).json({ received: true });
|
|
190
|
+
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.error("Inbound email processing error:", {
|
|
193
|
+
error: error.message,
|
|
194
|
+
stack: error.stack,
|
|
195
|
+
timestamp: new Date().toISOString(),
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Mailgun MUST receive 200 or it will retry
|
|
199
|
+
res.status(200).json({ received: true });
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
return router;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Create Express middleware for Mailgun inbound email webhook
|
|
208
|
+
*
|
|
209
|
+
* @param {Object} options - Configuration options
|
|
210
|
+
* @param {string} options.signingKey - Mailgun webhook signing key (or use MAILGUN_WEBHOOK_SIGNING_KEY env var)
|
|
211
|
+
* @param {Function} options.onEmailReceived - Callback function called when email is received (emailData) => {}
|
|
212
|
+
* @param {boolean} options.requireSignature - Whether to require signature verification (default: true)
|
|
213
|
+
* @returns {Array} Express middleware array
|
|
214
|
+
*/
|
|
215
|
+
function createMailgunInboundMiddleware(options = {}) {
|
|
216
|
+
const {
|
|
217
|
+
signingKey = process.env.MAILGUN_WEBHOOK_SIGNING_KEY,
|
|
218
|
+
onEmailReceived,
|
|
219
|
+
requireSignature = true,
|
|
220
|
+
} = options;
|
|
221
|
+
|
|
222
|
+
return [
|
|
223
|
+
express.urlencoded({ extended: true }),
|
|
224
|
+
upload.any(),
|
|
225
|
+
(req, res, next) => {
|
|
226
|
+
try {
|
|
227
|
+
const { emailData, token, timestamp, signature } = processEmailData(req);
|
|
228
|
+
|
|
229
|
+
// 🔐 Verify Mailgun authenticity
|
|
230
|
+
if (requireSignature && !verifyMailgunSignature(token, timestamp, signature, signingKey)) {
|
|
231
|
+
return res.status(401).json({ error: "Invalid Mailgun signature" });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Validate required fields
|
|
235
|
+
if (!emailData.from || !emailData.to || emailData.to.length === 0) {
|
|
236
|
+
console.error("Missing required email fields:", { from: emailData.from, to: emailData.to });
|
|
237
|
+
return res.status(200).json({ received: true, error: "Missing required fields" });
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Attach emailData to request object
|
|
241
|
+
req.emailData = emailData;
|
|
242
|
+
|
|
243
|
+
// Call user-provided callback if provided
|
|
244
|
+
if (onEmailReceived && typeof onEmailReceived === 'function') {
|
|
245
|
+
try {
|
|
246
|
+
onEmailReceived(emailData);
|
|
247
|
+
} catch (callbackError) {
|
|
248
|
+
console.error("Error in onEmailReceived callback:", callbackError);
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
// Default: log clean JSON data ready to save
|
|
252
|
+
console.log(JSON.stringify(emailData, null, 2));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
res.status(200).json({ received: true });
|
|
256
|
+
|
|
257
|
+
} catch (error) {
|
|
258
|
+
console.error("Inbound email processing error:", {
|
|
259
|
+
error: error.message,
|
|
260
|
+
stack: error.stack,
|
|
261
|
+
timestamp: new Date().toISOString(),
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Mailgun MUST receive 200 or it will retry
|
|
265
|
+
res.status(200).json({ received: true });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Export utilities and main functions
|
|
272
|
+
module.exports = {
|
|
273
|
+
createMailgunInboundRouter,
|
|
274
|
+
createMailgunInboundMiddleware,
|
|
275
|
+
processEmailData,
|
|
276
|
+
verifyMailgunSignature,
|
|
277
|
+
extractEmail,
|
|
278
|
+
extractEmails,
|
|
279
|
+
cleanMessageId,
|
|
280
|
+
parseHeaders,
|
|
281
|
+
};
|
|
282
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mailgun-inbound-email",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A reusable npm package for handling Mailgun inbound email webhooks with Express.js",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"mailgun",
|
|
11
|
+
"inbound",
|
|
12
|
+
"email",
|
|
13
|
+
"webhook",
|
|
14
|
+
"express",
|
|
15
|
+
"middleware"
|
|
16
|
+
],
|
|
17
|
+
"author": "",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"express": "^4.16.1",
|
|
21
|
+
"multer": "^2.0.2"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"express": "^4.0.0"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=14"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/RitikKumarSahoo/mailgun-inbound.git"
|
|
33
|
+
},
|
|
34
|
+
"type": "commonjs",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/RitikKumarSahoo/mailgun-inbound/issues"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/RitikKumarSahoo/mailgun-inbound#readme"
|
|
39
|
+
}
|