Skip to content

Commit 45062ce

Browse files
committed
fix: centralize and standardize error codes
- Add lib/errors.js with 21 documented error codes - Update all modules to use centralized error constants - Change inconsistent 'NoAuth' code to 'ENOAUTH' - Add error codes to previously uncoded errors in: - sendmail-transport (ESENDMAIL) - xoauth2 (EOAUTH2) - fetch (EFETCH) - http-proxy-client (EPROXY) - mailer (EPROXY) - mime-node (EFILEACCESS, EURLACCESS) - nodemailer (ECONFIG) - Add test/errors/errors-test.js BREAKING CHANGE: Error code 'NoAuth' renamed to 'ENOAUTH'
1 parent 8931195 commit 45062ce

14 files changed

Lines changed: 216 additions & 39 deletions

File tree

lib/errors.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
'use strict';
2+
3+
/**
4+
* Nodemailer Error Codes
5+
*
6+
* Centralized error code definitions for consistent error handling.
7+
*
8+
* Usage:
9+
* const errors = require('./errors');
10+
* let err = new Error('Connection closed');
11+
* err.code = errors.ECONNECTION;
12+
*/
13+
14+
/**
15+
* Error code descriptions for documentation and debugging
16+
*/
17+
const ERROR_CODES = {
18+
// Connection errors
19+
ECONNECTION: 'Connection closed unexpectedly',
20+
ETIMEDOUT: 'Connection or operation timed out',
21+
ESOCKET: 'Socket-level error',
22+
EDNS: 'DNS resolution failed',
23+
24+
// TLS/Security errors
25+
ETLS: 'TLS handshake or STARTTLS failed',
26+
EREQUIRETLS: 'REQUIRETLS not supported by server (RFC 8689)',
27+
28+
// Protocol errors
29+
EPROTOCOL: 'Invalid SMTP server response',
30+
EENVELOPE: 'Invalid mail envelope (sender or recipients)',
31+
EMESSAGE: 'Message delivery error',
32+
ESTREAM: 'Stream processing error',
33+
34+
// Authentication errors
35+
EAUTH: 'Authentication failed',
36+
ENOAUTH: 'Authentication credentials not provided',
37+
EOAUTH2: 'OAuth2 token generation or refresh error',
38+
39+
// Resource errors
40+
EMAXLIMIT: 'Pool resource limit reached (max messages per connection)',
41+
42+
// Transport-specific errors
43+
ESENDMAIL: 'Sendmail command error',
44+
ESES: 'AWS SES transport error',
45+
46+
// Configuration and access errors
47+
ECONFIG: 'Invalid configuration',
48+
EPROXY: 'Proxy connection error',
49+
EFILEACCESS: 'File access rejected (disableFileAccess is set)',
50+
EURLACCESS: 'URL access rejected (disableUrlAccess is set)',
51+
EFETCH: 'HTTP fetch error'
52+
};
53+
54+
// Export error codes as string constants and the full definitions object
55+
module.exports = Object.keys(ERROR_CODES).reduce(
56+
(exports, code) => {
57+
exports[code] = code;
58+
return exports;
59+
},
60+
{ ERROR_CODES }
61+
);

lib/fetch/index.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const PassThrough = require('stream').PassThrough;
88
const Cookies = require('./cookies');
99
const packageData = require('../../package.json');
1010
const net = require('net');
11+
const errors = require('../errors');
1112

1213
const MAX_REDIRECTS = 5;
1314

@@ -76,7 +77,7 @@ function nmfetch(url, options) {
7677
return;
7778
}
7879
finished = true;
79-
err.type = 'FETCH';
80+
err.code = errors.EFETCH;
8081
err.sourceUrl = url;
8182
fetchRes.emit('error', err);
8283
});
@@ -99,7 +100,7 @@ function nmfetch(url, options) {
99100
return;
100101
}
101102
finished = true;
102-
E.type = 'FETCH';
103+
E.code = errors.EFETCH;
103104
E.sourceUrl = url;
104105
fetchRes.emit('error', E);
105106
return;
@@ -147,7 +148,7 @@ function nmfetch(url, options) {
147148
} catch (E) {
148149
finished = true;
149150
setImmediate(() => {
150-
E.type = 'FETCH';
151+
E.code = errors.EFETCH;
151152
E.sourceUrl = url;
152153
fetchRes.emit('error', E);
153154
});
@@ -162,7 +163,7 @@ function nmfetch(url, options) {
162163
finished = true;
163164
req.abort();
164165
let err = new Error('Request Timeout');
165-
err.type = 'FETCH';
166+
err.code = errors.EFETCH;
166167
err.sourceUrl = url;
167168
fetchRes.emit('error', err);
168169
});
@@ -173,7 +174,7 @@ function nmfetch(url, options) {
173174
return;
174175
}
175176
finished = true;
176-
err.type = 'FETCH';
177+
err.code = errors.EFETCH;
177178
err.sourceUrl = url;
178179
fetchRes.emit('error', err);
179180
});
@@ -204,7 +205,7 @@ function nmfetch(url, options) {
204205
if (options.redirects > options.maxRedirects) {
205206
finished = true;
206207
let err = new Error('Maximum redirect count exceeded');
207-
err.type = 'FETCH';
208+
err.code = errors.EFETCH;
208209
err.sourceUrl = url;
209210
fetchRes.emit('error', err);
210211
req.abort();
@@ -222,7 +223,7 @@ function nmfetch(url, options) {
222223
if (res.statusCode >= 300 && !options.allowErrorResponse) {
223224
finished = true;
224225
let err = new Error('Invalid status code ' + res.statusCode);
225-
err.type = 'FETCH';
226+
err.code = errors.EFETCH;
226227
err.sourceUrl = url;
227228
fetchRes.emit('error', err);
228229
req.abort();
@@ -234,7 +235,7 @@ function nmfetch(url, options) {
234235
return;
235236
}
236237
finished = true;
237-
err.type = 'FETCH';
238+
err.code = errors.EFETCH;
238239
err.sourceUrl = url;
239240
fetchRes.emit('error', err);
240241
req.abort();
@@ -247,7 +248,7 @@ function nmfetch(url, options) {
247248
return;
248249
}
249250
finished = true;
250-
err.type = 'FETCH';
251+
err.code = errors.EFETCH;
251252
err.sourceUrl = url;
252253
fetchRes.emit('error', err);
253254
req.abort();
@@ -267,7 +268,7 @@ function nmfetch(url, options) {
267268
}
268269
} catch (err) {
269270
finished = true;
270-
err.type = 'FETCH';
271+
err.code = errors.EFETCH;
271272
err.sourceUrl = url;
272273
fetchRes.emit('error', err);
273274
return;

lib/mailer/index.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const mimeTypes = require('../mime-funcs/mime-types');
66
const MailComposer = require('../mail-composer');
77
const DKIM = require('../dkim');
88
const httpProxyClient = require('../smtp-connection/http-proxy-client');
9+
const errors = require('../errors');
910
const util = require('util');
1011
const urllib = require('url');
1112
const packageData = require('../../package.json');
@@ -337,7 +338,9 @@ class Mail extends EventEmitter {
337338
case 'socks4':
338339
case 'socks4a': {
339340
if (!this.meta.has('proxy_socks_module')) {
340-
return callback(new Error('Socks module not loaded'));
341+
let err = new Error('Socks module not loaded');
342+
err.code = errors.EPROXY;
343+
return callback(err);
341344
}
342345
let connect = ipaddress => {
343346
let proxyV2 = !!this.meta.get('proxy_socks_module').SocksClient;
@@ -394,7 +397,9 @@ class Mail extends EventEmitter {
394397
});
395398
}
396399
}
397-
callback(new Error('Unknown proxy configuration'));
400+
let err = new Error('Unknown proxy configuration');
401+
err.code = errors.EPROXY;
402+
callback(err);
398403
};
399404
}
400405

lib/mime-node/index.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const qp = require('../qp');
1313
const base64 = require('../base64');
1414
const addressparser = require('../addressparser');
1515
const nmfetch = require('../fetch');
16+
const errors = require('../errors');
1617
const LastNewline = require('./last-newline');
1718

1819
const LeWindows = require('./le-windows');
@@ -979,15 +980,23 @@ class MimeNode {
979980
} else if (content && typeof content.path === 'string' && !content.href) {
980981
if (this.disableFileAccess) {
981982
contentStream = new PassThrough();
982-
setImmediate(() => contentStream.emit('error', new Error('File access rejected for ' + content.path)));
983+
setImmediate(() => {
984+
let err = new Error('File access rejected for ' + content.path);
985+
err.code = errors.EFILEACCESS;
986+
contentStream.emit('error', err);
987+
});
983988
return contentStream;
984989
}
985990
// read file
986991
return fs.createReadStream(content.path);
987992
} else if (content && typeof content.href === 'string') {
988993
if (this.disableUrlAccess) {
989994
contentStream = new PassThrough();
990-
setImmediate(() => contentStream.emit('error', new Error('Url access rejected for ' + content.href)));
995+
setImmediate(() => {
996+
let err = new Error('Url access rejected for ' + content.href);
997+
err.code = errors.EURLACCESS;
998+
contentStream.emit('error', err);
999+
});
9911000
return contentStream;
9921001
}
9931002
// fetch URL

lib/nodemailer.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const SendmailTransport = require('./sendmail-transport');
88
const StreamTransport = require('./stream-transport');
99
const JSONTransport = require('./json-transport');
1010
const SESTransport = require('./ses-transport');
11+
const errors = require('./errors');
1112
const nmfetch = require('./fetch');
1213
const packageData = require('../package.json');
1314

@@ -49,7 +50,7 @@ module.exports.createTransport = function (transporter, defaults) {
4950
let error = new Error(
5051
'Using legacy SES configuration, expecting @aws-sdk/client-sesv2, see https://nodemailer.com/transports/ses/'
5152
);
52-
error.code = 'LegacyConfig';
53+
error.code = errors.ECONFIG;
5354
throw error;
5455
}
5556
transporter = new SESTransport(options);

lib/sendmail-transport/index.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const spawn = require('child_process').spawn;
44
const packageData = require('../../package.json');
55
const shared = require('../shared');
6+
const errors = require('../errors');
67

78
/**
89
* Generates a Transport object for Sendmail
@@ -72,7 +73,9 @@ class SendmailTransport {
7273
.concat(envelope.to || [])
7374
.some(addr => /^-/.test(addr));
7475
if (hasInvalidAddresses) {
75-
return done(new Error('Can not send mail. Invalid envelope addresses.'));
76+
let err = new Error('Can not send mail. Invalid envelope addresses.');
77+
err.code = errors.ESENDMAIL;
78+
return done(err);
7679
}
7780

7881
if (this.args) {
@@ -141,6 +144,7 @@ class SendmailTransport {
141144
} else {
142145
err = new Error('Sendmail exited with code ' + code);
143146
}
147+
err.code = errors.ESENDMAIL;
144148

145149
this.logger.error(
146150
{
@@ -202,7 +206,9 @@ class SendmailTransport {
202206

203207
sourceStream.pipe(sendmail.stdin);
204208
} else {
205-
return callback(new Error('sendmail was not found'));
209+
let err = new Error('sendmail was not found');
210+
err.code = errors.ESENDMAIL;
211+
return callback(err);
206212
}
207213
}
208214
}

lib/smtp-connection/http-proxy-client.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
const net = require('net');
88
const tls = require('tls');
99
const urllib = require('url');
10+
const errors = require('../errors');
1011

1112
/**
1213
* Establishes proxied connection to destinationPort
@@ -121,7 +122,9 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, callback) {
121122
} catch (_E) {
122123
// ignore
123124
}
124-
return callback(new Error('Invalid response from proxy' + ((match && ': ' + match[1]) || '')));
125+
let err = new Error('Invalid response from proxy' + ((match && ': ' + match[1]) || ''));
126+
err.code = errors.EPROXY;
127+
return callback(err);
125128
}
126129

127130
socket.removeListener('error', tempSocketErr);

lib/smtp-pool/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const PoolResource = require('./pool-resource');
55
const SMTPConnection = require('../smtp-connection');
66
const wellKnown = require('../well-known');
77
const shared = require('../shared');
8+
const errors = require('../errors');
89
const packageData = require('../../package.json');
910

1011
/**
@@ -633,7 +634,7 @@ class SMTPPool extends EventEmitter {
633634
});
634635
} else if (!auth && connection.allowsAuth && options.forceAuth) {
635636
let err = new Error('Authentication info was not provided');
636-
err.code = 'NoAuth';
637+
err.code = errors.ENOAUTH;
637638

638639
returned = true;
639640
connection.close();

lib/smtp-pool/pool-resource.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const SMTPConnection = require('../smtp-connection');
44
const assign = require('../shared').assign;
55
const XOAuth2 = require('../xoauth2');
6+
const errors = require('../errors');
67
const EventEmitter = require('events');
78

89
/**
@@ -121,7 +122,7 @@ class PoolResource extends EventEmitter {
121122
let err = new Error('Unexpected socket close');
122123
if (this.connection && this.connection._socket && this.connection._socket.upgrading) {
123124
// starttls connection errors
124-
err.code = 'ETLS';
125+
err.code = errors.ETLS;
125126
}
126127
callback(err);
127128
}, 1000);
@@ -226,7 +227,7 @@ class PoolResource extends EventEmitter {
226227
let err;
227228
if (this.messages >= this.options.maxMessages) {
228229
err = new Error('Resource exhausted');
229-
err.code = 'EMAXLIMIT';
230+
err.code = errors.EMAXLIMIT;
230231
this.connection.close();
231232
this.emit('error', err);
232233
} else {

lib/smtp-transport/index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const SMTPConnection = require('../smtp-connection');
55
const wellKnown = require('../well-known');
66
const shared = require('../shared');
77
const XOAuth2 = require('../xoauth2');
8+
const errors = require('../errors');
89
const packageData = require('../../package.json');
910

1011
/**
@@ -190,7 +191,7 @@ class SMTPTransport extends EventEmitter {
190191
let err = new Error('Unexpected socket close');
191192
if (connection && connection._socket && connection._socket.upgrading) {
192193
// starttls connection errors
193-
err.code = 'ETLS';
194+
err.code = errors.ETLS;
194195
}
195196
callback(err);
196197
}, 1000);
@@ -392,7 +393,7 @@ class SMTPTransport extends EventEmitter {
392393
});
393394
} else if (!authData && connection.allowsAuth && options.forceAuth) {
394395
let err = new Error('Authentication info was not provided');
395-
err.code = 'NoAuth';
396+
err.code = errors.ENOAUTH;
396397

397398
returned = true;
398399
connection.close();

0 commit comments

Comments
 (0)