Haraka 3.1.5 → 3.1.7

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.
Files changed (79) hide show
  1. package/.prettierignore +1 -1
  2. package/{Changes.md → CHANGELOG.md} +54 -3
  3. package/CONTRIBUTORS.md +26 -26
  4. package/Plugins.md +99 -99
  5. package/README.md +68 -93
  6. package/SECURITY.md +178 -0
  7. package/bin/haraka +7 -14
  8. package/config/plugins +0 -3
  9. package/config/smtp_forward.ini +10 -0
  10. package/config/smtp_proxy.ini +10 -0
  11. package/connection.js +25 -8
  12. package/docs/Connection.md +126 -39
  13. package/docs/CoreConfig.md +92 -74
  14. package/docs/HAProxy.md +41 -25
  15. package/docs/Logging.md +68 -38
  16. package/docs/Outbound.md +124 -179
  17. package/docs/Plugins.md +38 -59
  18. package/docs/Transaction.md +78 -83
  19. package/docs/Tutorial.md +122 -209
  20. package/docs/plugins/aliases.md +1 -141
  21. package/docs/plugins/auth/auth_ldap.md +2 -39
  22. package/docs/plugins/max_unrecognized_commands.md +4 -18
  23. package/docs/plugins/process_title.md +3 -3
  24. package/docs/plugins/queue/smtp_forward.md +19 -3
  25. package/docs/plugins/queue/smtp_proxy.md +10 -2
  26. package/docs/plugins/reseed_rng.md +11 -13
  27. package/docs/plugins/tls.md +7 -7
  28. package/docs/plugins/toobusy.md +10 -4
  29. package/docs/tutorials/SettingUpOutbound.md +40 -48
  30. package/endpoint.js +32 -2
  31. package/haraka.js +1 -1
  32. package/outbound/hmail.js +42 -41
  33. package/outbound/index.js +7 -4
  34. package/outbound/tls.js +2 -43
  35. package/package.json +51 -61
  36. package/plugins/auth/auth_base.js +9 -3
  37. package/plugins/auth/auth_proxy.js +14 -11
  38. package/plugins/block_me.js +4 -2
  39. package/plugins/prevent_credential_leaks.js +3 -1
  40. package/plugins/process_title.js +6 -6
  41. package/plugins/queue/qmail-queue.js +15 -19
  42. package/plugins/queue/smtp_forward.js +12 -4
  43. package/plugins/queue/smtp_proxy.js +14 -3
  44. package/plugins/tls.js +13 -5
  45. package/plugins/xclient.js +3 -1
  46. package/server.js +22 -10
  47. package/smtp_client.js +20 -11
  48. package/test/config/block_me.recipient +1 -0
  49. package/test/config/block_me.senders +1 -0
  50. package/test/connection.js +258 -0
  51. package/test/endpoint.js +27 -0
  52. package/test/outbound/bounce_net_errors.js +3 -2
  53. package/test/outbound/hmail.js +19 -0
  54. package/test/outbound/index.js +189 -0
  55. package/test/outbound/queue.js +92 -0
  56. package/test/plugins/auth/auth_bridge.js +80 -0
  57. package/test/plugins/auth/flat_file.js +128 -0
  58. package/test/plugins/block_me.js +157 -0
  59. package/test/plugins/data.signatures.js +114 -0
  60. package/test/plugins/delay_deny.js +263 -0
  61. package/test/plugins/prevent_credential_leaks.js +178 -0
  62. package/test/plugins/process_title.js +135 -0
  63. package/test/plugins/queue/deliver.js +99 -0
  64. package/test/plugins/queue/discard.js +79 -0
  65. package/test/plugins/queue/lmtp.js +138 -0
  66. package/test/plugins/queue/qmail-queue.js +99 -0
  67. package/test/plugins/queue/quarantine.js +81 -0
  68. package/test/plugins/queue/smtp_bridge.js +154 -0
  69. package/test/plugins/queue/smtp_forward.js +42 -6
  70. package/test/plugins/queue/smtp_proxy.js +139 -0
  71. package/test/plugins/reseed_rng.js +34 -0
  72. package/test/plugins/tarpit.js +91 -0
  73. package/test/plugins/tls.js +25 -0
  74. package/test/plugins/toobusy.js +21 -0
  75. package/test/plugins/xclient.js +14 -0
  76. package/test/server.js +231 -0
  77. package/test/smtp_client.js +45 -12
  78. package/test/tls_socket.js +220 -0
  79. package/tls_socket.js +52 -2
package/docs/Logging.md CHANGED
@@ -1,83 +1,109 @@
1
1
  # Haraka Logging
2
2
 
3
- Haraka has built-in logging (see API docs below) and support for log plugins.
3
+ Haraka has a built-in logger (described below) and a plugin hook (`log`) that lets log plugins ship messages elsewhere — for example to syslog via [haraka-plugin-syslog](https://github.com/haraka/haraka-plugin-syslog).
4
4
 
5
- - log.ini
5
+ ## Configuration
6
6
 
7
- Contains settings for log level, timestamps, and format. See the example log.ini file for examples.
7
+ ### log.ini
8
8
 
9
- - loglevel
9
+ ```ini
10
+ [main]
11
+ ; data, protocol, debug, info, notice, warn, error, crit, alert, emerg
12
+ level=info
10
13
 
11
- The loglevel file provides a finger-friendly way to change the loglevel on the CLI. Use it like so: `echo DEBUG > config/loglevel`. When the level in log.ini is set and the loglevel file is present, the loglevel file wins. During runtime, whichever was edited most recently wins.
14
+ ; prepend ISO-8601 timestamps to log entries (built-in logger only)
15
+ timestamps=false
12
16
 
13
- ## Logging API
17
+ ; default, logfmt, json
18
+ format=default
19
+ ```
14
20
 
15
- Logging conventions within Haraka
21
+ ### loglevel
16
22
 
17
- This section pertains to the built in logging. For log plugins like ([haraka-plugin-syslog](https://github.com/haraka/haraka-plugin-syslog)), refer to the plugin's docs.
23
+ A single-line file for quick CLI tweaks:
18
24
 
19
- The logline by default will be in the form of:
25
+ ```sh
26
+ echo DEBUG > config/loglevel
27
+ ```
20
28
 
21
- [level] [uuid] [origin] message
29
+ When both `log.ini` and the `loglevel` file are present, whichever was edited most recently wins at runtime — `loglevel` is convenient for an interactive bump without touching `log.ini`.
22
30
 
23
- Where origin is "core" or the name of the plugin which triggered the message, and "uuid" is the ID of the connection associated with the message.
31
+ ### log_timestamps
24
32
 
25
- When calling a log method on logger, you should provide the plugin object and the connection object anywhere in the arguments
26
- to the log method.
33
+ A single-value file that toggles timestamp prepending. Equivalent to `main.timestamps` in `log.ini`. If either source enables timestamps, they are enabled.
27
34
 
28
- logger.logdebug("i like turtles", plugin, connection);
35
+ ## Log Levels
29
36
 
30
- Will yield, for example,
37
+ In ascending severity (and decreasing verbosity):
31
38
 
32
- [DEBUG] [7F1C820F-DC79-4192-9AA6-5307354B20A6] [dnsbl] i like turtles
39
+ | Level | Numeric | Use |
40
+ | -------- | ------- | --- |
41
+ | DATA | 9 | message body bytes — extremely verbose |
42
+ | PROTOCOL | 8 | SMTP wire protocol |
43
+ | DEBUG | 7 | developer diagnostics |
44
+ | INFO | 6 | general informational |
45
+ | NOTICE | 5 | normal but significant events (connect/disconnect, summary lines) |
46
+ | WARN | 4 | recoverable problems |
47
+ | ERROR | 3 | non-fatal errors |
48
+ | CRIT | 2 | critical errors |
49
+ | ALERT | 1 | needs immediate attention |
50
+ | EMERG | 0 | unusable |
33
51
 
34
- If you call the log method on the connection object, you can forego the connection as argument:
52
+ A message is emitted when its level the configured level.
35
53
 
36
- connection.logdebug("turtles all the way down", plugin);
54
+ ## Logging API
37
55
 
38
- and similarly for the log methods on the plugin object:
56
+ Every log call ultimately produces:
39
57
 
40
- plugin.logdebug("he just really likes turtles", connection);
58
+ [level] [uuid] [origin] message
41
59
 
42
- failing to provide a connection and/or plugin object will leavethe default values in the log (currently "core").
60
+ `origin` is `core` or the plugin name; `uuid` is the connection UUID (with `.N` appended for the Nth transaction).
43
61
 
44
- This is implemented by testing for argument type in the logger.js log\* method. objects-as-arguments are then sniffed to try to determine if they're a connection or plugin instance.
62
+ The simplest call is on the connection or plugin object origin and uuid are filled in automatically:
45
63
 
46
- ### Log formats
64
+ ```js
65
+ connection.logdebug('turtles all the way down')
66
+ plugin.loginfo('checking sender', connection)
67
+ ```
47
68
 
48
- Apart from the default log format described above, Haraka also supports logging as [`logfmt`](https://brandur.org/logfmt) and JSON. These can be used by changing the `format` attribute in `log.ini` to the desired format, e.g.:
69
+ Each of the level names has a matching method:
70
+ `logdata`, `logprotocol`, `logdebug`, `loginfo`, `lognotice`, `logwarn`,
71
+ `logerror`, `logcrit`, `logalert`, `logemerg`.
49
72
 
50
- ```ini
51
- ; format=default
52
- ; format=json
53
- format=logfmt
73
+ Calling the logger directly works too — pass the plugin and/or connection anywhere in the arguments and the logger sniffs them:
74
+
75
+ ```js
76
+ logger.logdebug('i like turtles', plugin, connection)
77
+ // → [DEBUG] [7F1C820F-…] [dnsbl] i like turtles
54
78
  ```
55
79
 
56
- Here's an example of a log line generated with `logfmt`:
80
+ Plain objects mixed into the arguments are merged into the log record (in `logfmt` / `json` formats) or stringified (`key=value` pairs in the default format).
81
+
82
+ ## Log Formats
83
+
84
+ Set `main.format` in `log.ini` to one of `default`, `logfmt`, or `json`.
85
+
86
+ `logfmt`:
57
87
 
58
- level=PROTOCOL uuid=9FF7F70E-5D57-435A-AAD9-EA069B6159D9.1 source=core message="S: 354 go ahead, make my day"
88
+ level=PROTOCOL uuid=9FF7F70E-…1 source=core message="S: 354 go ahead, make my day"
59
89
 
60
- And the same line formatted as JSON:
90
+ `json`:
61
91
 
62
92
  ```json
63
93
  {
64
94
  "level": "PROTOCOL",
65
- "uuid": "9FF7F70E-5D57-435A-AAD9-EA069B6159D9.1",
95
+ "uuid": "9FF7F70E-…1",
66
96
  "source": "core",
67
97
  "message": "S: 354 go ahead, make my day"
68
98
  }
69
99
  ```
70
100
 
71
- Any objects passed to the log methods will also have their properties included in the log line. For example, using `logfmt`:
72
-
73
- level=NOTICE uuid=9FF7F70E-5D57-435A-AAD9-EA069B6159D9.1 source=core message=disconnect ip=127.0.0.1 rdns=Unknown helo=3h2dnz8a0if relay=N early=N esmtp=N tls=N pipe=N errors=0 txns=1 rcpts=1/0/0 msgs=1/0/0 bytes=222 lr="" time=0.052
74
-
75
- And using JSON:
101
+ A typical structured disconnect line looks like:
76
102
 
77
103
  ```json
78
104
  {
79
105
  "level": "NOTICE",
80
- "uuid": "9FF7F70E-5D57-435A-AAD9-EA069B6159D9.1",
106
+ "uuid": "9FF7F70E-…1",
81
107
  "source": "core",
82
108
  "message": "disconnect",
83
109
  "ip": "127.0.0.1",
@@ -97,3 +123,7 @@ And using JSON:
97
123
  "time": 0.052
98
124
  }
99
125
  ```
126
+
127
+ ## The `log` hook
128
+
129
+ Each log message becomes a `log` hook invocation. The built-in handler writes to stdout (with ANSI colour when stdout is a TTY); log plugins can return `OK` or `STOP` to suppress the built-in output and ship the message elsewhere. Messages emitted before plugins finish loading are buffered and replayed once the plugin chain is ready.
package/docs/Outbound.md CHANGED
@@ -1,265 +1,210 @@
1
1
  # Outbound Mail with Haraka
2
2
 
3
- A default installation of Haraka will queue outbound mail for delivery in the queue directory. Those mails will be delivered to the appropriate MX record for that domain. Mails are queued onto your disk, and will deal appropriately with temporary failures to retry delivery later.
3
+ A default Haraka installation queues outbound mail to disk and delivers it to the appropriate MX for each recipient domain. Temporary failures are retried automatically using the configured backoff schedule.
4
4
 
5
- Outbound mails are defined as those that have set the `connection.relaying` flag to `true` via a plugin. The simplest way of doing that is to use SMTP AUTH, and have the client authenticate. For example using the `auth/flat_file` plugin. The `relay` plugin provides common ways to set it and it is simple to write a custom plugin to do this.
5
+ A mail is treated as outbound when a plugin sets `connection.relaying` to `true`. The simplest way is SMTP AUTH using `auth/flat_file` or one of the [auth plugins](plugins/auth/); the `relay` plugin offers allow-list-based variants, and a custom plugin can apply any policy.
6
6
 
7
- For statistics on outbound mail use the `process_title` plugin. See the documentation for that plugin for details.
7
+ For live stats on the outbound queue see the [`process_title`](plugins/process_title.md) plugin.
8
8
 
9
- To flush the outbound queue (for temporary failed mails) hit the Haraka master process with the SIGHUP signal (via the `kill` command line tool).
9
+ To flush the temp-fail queue (e.g. after fixing network or DNS), send `SIGHUP` to the Haraka master process.
10
10
 
11
- ## Outbound Configuration Files
11
+ ## Outbound Configuration
12
12
 
13
13
  ### outbound.ini
14
14
 
15
- - `disabled`
16
-
17
- Default: false. Allows one to temporarily disable outbound delivery, while still receiving and queuing emails. This can be changed while Haraka is running.
18
-
19
- - `concurrency_max`
20
-
21
- Default: 100. Specifies the maximum concurrent connections to make. Note that if using cluster (multiple CPUs) this will be multiplied by the number of CPUs that you have.
22
-
23
- - `enable_tls`
24
-
25
- Default: true. Switch to false to disable TLS for outbound mail.
26
-
27
- This uses the same `tls_key.pem` and `tls_cert.pem` files that the `TLS` plugin uses, along with other values in `tls.ini`. See the [TLS plugin docs][url-tls] for more information.
28
-
29
- Within `tls.ini` you can specify global options for the values `ciphers`, `minVersion`, `requestCert` and `rejectUnauthorized`, alternatively you can provide separate values by putting them under a key: `[outbound]`, such as:
15
+ | Key | Default | Description |
16
+ | --- | --- | --- |
17
+ | `disabled` | `false` | Pause outbound delivery while still queuing inbound mail. Reloadable at runtime. |
18
+ | `concurrency_max` | `10000` | Maximum concurrent outbound deliveries **per worker**. Effective total is `concurrency_max × nodes`. |
19
+ | `enable_tls` | `true` | Use opportunistic STARTTLS on outbound. |
20
+ | `maxTempFailures` | `13` | Maximum temp-fail retries before the message bounces. Ignored if `temp_fail_intervals` is set. |
21
+ | `temp_fail_intervals` | derived | Comma-separated `<n><unit>[*<count>]` pattern. `1m, 5m*2, 1h*3` `[60,300,300,3600,3600,3600]` seconds. `none` bounces on first temp-fail. |
22
+ | `always_split` | `false` | Create one queue file per recipient (instead of one per destination domain). Hurts throughput but simplifies bounce handling. |
23
+ | `received_header` | `Haraka outbound` | Text used in the outbound `Received:` header. Set to the literal `disabled` to omit it. |
24
+ | `connect_timeout` | `30` | Seconds to wait for TCP connect to the remote MX. |
25
+ | `local_mx_ok` | `false` | Allow outbound delivery to local/private IPs (otherwise blocked to prevent loops). |
26
+ | `inet_prefer` | `default` | `default` (prefer IPv6 at equal MX priority), `v4`, or `v6`. Delivery still follows MX priority. |
27
+
28
+ TLS configuration is shared with the `tls` plugin (`tls_key.pem`, `tls_cert.pem`, and `tls.ini`). Outbound-specific overrides go under `[outbound]` in `tls.ini`:
30
29
 
31
30
  ```ini
32
31
  [outbound]
33
32
  ciphers=!DES
33
+ minVersion=TLSv1.2
34
34
  ```
35
35
 
36
- - `always_split`
37
-
38
- Default: false. By default, Haraka groups message recipients by domain so that messages with multiple recipients at the same domain get sent in a single SMTP session. When `always_split` is enabled, each recipient gets a queue entry and delivery in its own SMTP session. This carries a performance penalty but enables more flexibility in mail delivery and bounce handling.
39
-
40
- - `received_header`
41
-
42
- Default: "Haraka outbound". If this text is any string except _disabled_, the string is attached as a `Received` header to all outbound mail just before it is queued.
43
-
44
- - `connect_timeout`
45
-
46
- Timeout for connecting to remote servers. Default: 30s
47
-
48
- - `local_mx_ok`
49
-
50
- Default: false. By default, outbound to a local IP is disabled, to avoid creating mail loops. Set this to true if you want to allow outbound to local IPs. This could be useful if you want to deliver mail to private IPs or localhost on another port.
51
-
52
- - `temp_fail_intervals`
53
-
54
- Set this to specify the delay intervals to use between trying to re-send an email that has a temporary failure condition. The setting is a comma separated list of time spans and multipliers. The time span is a number followed by `s`, `m`, `h`, or `d` to represent seconds, minutes, hours, and days, respectively. The multiplier is an asterisk followed by an integer representing the number of times to repeat the interval. For example, the entry `1m, 5m*2, 1h*3` results in an array of delay times of `[60,300,300,3600,3600,3600]` in seconds. The email will be bounced when the array runs out of intervals (the 7th failure in this case). Set this to `none` to bounce the email on the first temporary failure.
55
-
56
- * `inet_prefer`
57
-
58
- Default: default. Selects the preferred address family (IP version) to deliver messages.
59
-
60
- | Value | Description |
61
- |------------------------|-------------|
62
- | `default` | Prefer IPv6 when IPv4 and IPv6 IPs exist at the same MX priority |
63
- | `v4` | Try IPv4 addresses first, then IPv6 |
64
- | `v6` | Try IPv6 addresses first, then IPv4 |
65
-
66
- Note: Delivery attempts follow MX priority order. Socket-based deliveries ignore this setting.
67
-
68
36
  ### outbound.bounce_message
69
37
 
70
- See "Bounce Messages" below for details.
38
+ Template for the bounce message body. See "Bounce Messages" below.
71
39
 
72
40
  ## The HMail Object
73
41
 
74
- Many hooks (see below) pass in a `hmail` object.
42
+ Most outbound hooks pass an `hmail` (HMailItem). You rarely need its methods, but these properties are useful:
75
43
 
76
- You likely won't ever need to call methods on this object, so they are left undocumented here.
44
+ | Property | Description |
45
+ | --- | --- |
46
+ | `path` | Full filesystem path to the queue file. |
47
+ | `filename` | Queue file's base name. |
48
+ | `num_failures` | Number of temp-fail attempts so far. |
49
+ | `notes` | Plain object for plugin state, scoped to this queue item. |
50
+ | `todo` | The `TODOItem` describing what to deliver (see below). |
77
51
 
78
- The attributes of an `hmail` object that may be of use are:
52
+ ## The TODO Object
79
53
 
80
- - path - the full path to the queue file
81
- - filename - the filename within the queue dir
82
- - num_failures - the number of times this mail has been temp failed
83
- - notes - notes you can store on a hmail object (similar to `transaction.notes`) to allow you to pass information between outbound hooks
84
- - todo - see below
54
+ `hmail.todo` describes the delivery:
85
55
 
86
- ## The ToDo Object
56
+ | Property | Description |
57
+ | --- | --- |
58
+ | `mail_from` | `Address`<sup>[1](#fn1)</sup> — the envelope sender. |
59
+ | `rcpt_to` | `Address`<sup>[1](#fn1)</sup> array — envelope recipients. |
60
+ | `domain` | Recipient domain (a single domain unless `always_split` is set). |
61
+ | `notes` | The original `transaction.notes`. Keys you may set: |
62
+ | `notes.outbound_ip` | IP to bind the outbound socket to. **Set via the `get_mx` hook**, not directly. |
63
+ | `notes.outbound_helo` | EHLO domain. **Set via the `get_mx` hook**, not directly. |
64
+ | `queue_time` | When the mail was queued (epoch ms). |
65
+ | `uuid` | Inherited from the source `transaction.uuid`. |
66
+ | `force_tls` | If `true`, defer instead of delivering in plaintext. |
87
67
 
88
- The `todo` object contains information about how to deliver this mail. Keys you may be interested in are:
68
+ ## Outbound Hooks
89
69
 
90
- - rcpt_to - an Array of `Address`<sup>[1](#fn1)</sup> objects - the rfc.2821 recipients of this mail
91
- - mail_from - an Address<sup>[1](#fn1)</sup> object - the rfc.2821 sender of this mail
92
- - domain - the domain this mail is going to (see `always_split` above)
93
- - notes - the original transaction.notes for this mail, also contains the following useful keys:
94
- - outbound_ip - the IP address to bind to (do not set manually, use the `get_mx` hook)
95
- - outbound_helo - the EHLO domain to use (again, do not set manually)
96
- - queue_time - the epoch milliseconds time when this mail was queued
97
- - uuid - the original transaction.uuid
98
- - force_tls - if true, this mail will be sent over TLS or defer
70
+ ### queue_outbound
99
71
 
100
- ## Outbound Mail Hooks
72
+ Runs before queuing. Returning `CONT` (or having no hook) queues the mail. `OK` indicates the plugin queued it itself; the `DENY*` codes reject the message.
101
73
 
102
- ### The queue_outbound hook
74
+ ### pre_send_trans_email
103
75
 
104
- The first hook that is called prior to queueing an outbound mail is the `queue_outbound` hook. Only if all these hooks return `CONT` (or if there are no hooks) will the mail be queued for outbound delivery. A return of `OK` will indicate that the mail has been queued in some custom manner for outbound delivery. Any of the `DENY` return codes will cause the message to be appropriately rejected.
76
+ Parameters: `next, connection`
105
77
 
106
- ### The send_email hook
78
+ Fired by `outbound.send_trans_email()` before the transaction is serialized to disk. Useful for plugins that synthesize mail programmatically — they can attach final headers or notes here.
107
79
 
108
- Parameters: `next, hmail`
80
+ ### send_email
109
81
 
110
- Called just as the email is about to be sent.
82
+ Parameters: `next, hmail`
111
83
 
112
- Respond with `next(DELAY, delay_seconds)` to defer sending the email at this time.
84
+ Called just before delivery starts. `next(DELAY, seconds)` defers the attempt.
113
85
 
114
- ### The get_mx hook
86
+ ### get_mx
115
87
 
116
88
  Parameters: `next, hmail, domain`
117
89
 
118
- Upon starting delivery the `get_mx` hook is called, with the parameter set to the domain in question (for example a mail to `user@example.com` will call the `get_mx` hook with `(next, hmail, domain)` as parameters). This is to allow you to implement a custom handler to find MX records. For most installations there is no reason to implement this hook - Haraka will find the MX records via DNS.
119
-
120
- The MX is sent via next(OK, mx). `mx` is a [HarakaMx][url-harakamx] object, an array of HarakaMx objects, or any suitable HarakaMx input.
121
-
122
- ### The deferred hook
90
+ Called when delivery begins, with the destination domain. Plugins can override MX lookup; most installs leave Haraka to do DNS. Respond with `next(OK, mx)` where `mx` is a [HarakaMx][url-harakamx] object, an array of them, or any HarakaMx-compatible input. Set `mx.auth_user` / `mx.auth_pass` to AUTH against the remote, or `mx.bind` / `mx.bind_helo`
91
+ to control source address and EHLO.
123
92
 
124
- Parameters: `next, hmail, {delay: ..., err: ...}`
93
+ ### deferred
125
94
 
126
- If the mail is temporarily deferred, the `deferred` hook is called. The hook parameter is an object with keys: `delay` and `err`, which explain the delay (in seconds) and error message.
95
+ Parameters: `next, hmail, { delay, err }`
127
96
 
128
- If you want to stop at this point, and drop the mail completely, then you can call `next(OK)`.
97
+ Fired on temporary failure. Return `OK` to drop the mail silently; return `DENYSOFT, seconds` to override the retry delay (useful for custom backoff indexed on `hmail.num_failures`).
129
98
 
130
- If you want to change the delay, then call `next(DENYSOFT, delay_in_seconds)`. Using this you can define a custom delay algorithm indexed by `hmail.num_failures`.
131
-
132
- ### The bounce hook
99
+ ### bounce
133
100
 
134
101
  Parameters: `next, hmail, error`
135
102
 
136
- If the mail completely bounces then the `bounce` hook is called. This is _not_ called if the mail is issued a temporary failure (a 4xx error code). The hook parameter is the error message received from the remote end as an `Error` object. The object may also have the following properties:
137
-
138
- - mx - the MX object that caused the bounce
139
- - deferred_rcpt - the deferred recipients that eventually bounced
140
- - bounced_rcpt - the bounced recipients
141
-
142
- If you do not wish to have a bounce message sent to the originating sender of the email then you can return `OK` from this hook to stop it from sending a bounce message.
143
-
144
- ### The delivered hook
145
-
146
- Parameters: `next, hmail, params`
103
+ Fired on permanent failure (5xx). Not called for temp-fails. `error` may carry:
147
104
 
148
- Params is a list of: `[host, ip, response, delay, port, mode, ok_recips, secured]`
105
+ - `mx` the MX that caused the bounce
106
+ - `deferred_rcpt` — recipients that eventually bounced after deferrals
107
+ - `bounced_rcpt` — recipients that bounced outright
149
108
 
150
- When mails are successfully delivered to the remote end then the `delivered` hook is called. The return codes from this hook have no effect, so it is only useful for logging the fact that a successful delivery occurred.
109
+ Return `OK` to suppress the DSN to the original sender.
151
110
 
152
- - `host` - Hostname of the MX that the message was delivered to,
153
- - `ip` - IP address of the host that the message was delivered to,
154
- - `response` - Variable contains the SMTP response text returned by the host that received the message and will typically contain the remote queue ID and
155
- - `delay` - Time taken between the queue file being created and the message being delivered.
156
- - `port` - Port number that the message was delivered to.
157
- - `mode` - Shows whether SMTP or LMTP was used to deliver the mail.
158
- - `ok_recips` - an `Address`<sup>[1](#fn1)</sup> array containing all of the recipients that were successfully delivered to.
159
- - `secured` - A boolean denoting if the connection used TLS or not.
111
+ ### delivered
160
112
 
161
- ## Outbound IP address
113
+ Parameters: `next, hmail, [host, ip, response, delay, port, mode, ok_recips, secured, authenticated]`
162
114
 
163
- Normally the OS will decide which IP address will be used for outbound connections using the IP routing table.
115
+ Fired after a successful delivery. Return codes are ignored; the hook is for logging / accounting.
164
116
 
165
- There are instances where you may want to separate outbound traffic on different IP addresses based on sender, domain or some other identifier. To do this, the IP address that you want to use _must_ be bound to an interface (or alias) on the local system.
117
+ | Element | Description |
118
+ | --- | --- |
119
+ | `host` | Hostname of the receiving MX. |
120
+ | `ip` | IP we delivered to. |
121
+ | `response` | Remote SMTP response text (typically includes the remote queue ID). |
122
+ | `delay` | Seconds between queue write and delivery. |
123
+ | `port` | Destination port. |
124
+ | `mode` | `'smtp'` or `'lmtp'`. |
125
+ | `ok_recips` | `Address`<sup>[1](#fn1)</sup> array of successfully delivered recipients. |
126
+ | `secured` | `true` if STARTTLS succeeded. |
127
+ | `authenticated` | `true` if outbound AUTH succeeded. |
166
128
 
167
- As described above, the outbound IP can be set using the `bind` parameter and also the outbound helo for the IP can be set using the `bind_ehlo` parameter returned by the `get_mx` hook.
129
+ ## Outbound IP Address
168
130
 
169
- ## AUTH
131
+ By default the OS routing table chooses the source IP. To pin outbound to a specific IP (per-sender, per-domain, etc.), bind that address to a local interface or alias, then set `mx.bind` (source IP) and `mx.bind_helo` (EHLO domain) in your `get_mx` hook.
170
132
 
171
- If you wish to use AUTH for a particular domain or domains, or you wish to force all mail to an outbound service or smart host that requires authentication then you can use the `get_mx` hook documented above to do this by supplying both `auth_user` and `auth_pass` properties in an MX object.
133
+ ## Outbound AUTH
172
134
 
173
- If AUTH properties are supplied and the remote end does not offer AUTH or there are no compatible AUTH methods, then the message will be sent without AUTH and a warning will be logged.
135
+ Force AUTH for a domain or smart host by returning an MX with `auth_user` and `auth_pass` set from the `get_mx` hook. If the remote end does not advertise AUTH (or no compatible mechanism is found), delivery proceeds without AUTH and a warning is logged.
174
136
 
175
137
  ## Bounce Messages
176
138
 
177
- The contents of the bounce message are configured by a file called `config/outbound.bounce_message`. If you look at this file you will see it contains several template entries wrapped in curly brackets. These will be populated as follows:
139
+ The bounce body comes from `config/outbound.bounce_message`. Curly-brace template variables are filled in at bounce time:
178
140
 
179
- Optional: Possibility to add HTML code (with optional image) to the bounce message is possible by adding the files `config/outbound.bounce_message_html`. An image can be attached to the mail by using `config/outbound.bounce_message_image`.
141
+ - `pid` current process id
142
+ - `date` — bounce timestamp
143
+ - `me` — contents of `config/me`
144
+ - `from` — original sender
145
+ - `msgid` — original message UUID
146
+ - `to` — original recipient (or first, for multi-recipient mail)
147
+ - `reason` — remote server's rejection text
180
148
 
181
- - pid - the current process id
182
- - date - the current date when the bounce occurred
183
- - me - the contents of `config/me`
184
- - from - the originating sender of the message
185
- - msgid - a uuid for the mail
186
- - to - the end recipient of the message, or the first recipient if it was to
187
- multiple people
188
- - reason - the text from the remote server indicating why it bounced
149
+ The original message is appended to the bounce.
189
150
 
190
- Following the bounce message itself will be a copy of the entire original message.
151
+ For HTML bounces, add `config/outbound.bounce_message_html` (and optionally an inline image in `config/outbound.bounce_message_image`).
191
152
 
192
- ## Creating a mail internally for outbound delivery
153
+ ## Generating Mail from a Plugin
193
154
 
194
- Sometimes it is necessary to generate a new mail from within a plugin.
195
-
196
- To do that, you can use the `outbound` module directly:
155
+ To create and queue a new message from inside a plugin, use the `outbound` module:
197
156
 
198
157
  ```js
199
158
  const outbound = require('./outbound')
200
159
 
201
- const to = 'user@example.com'
202
160
  const from = 'sender@example.com'
161
+ const to = 'user@example.com'
203
162
 
204
163
  const contents = [
205
- 'From: ' + from,
206
- 'To: ' + to,
207
- 'MIME-Version: 1.0',
208
- 'Content-type: text/plain; charset=us-ascii',
209
- 'Subject: Some subject here',
210
- '',
211
- 'Some email body here',
212
- '',
164
+ `From: ${from}`,
165
+ `To: ${to}`,
166
+ 'MIME-Version: 1.0',
167
+ 'Content-Type: text/plain; charset=us-ascii',
168
+ 'Subject: Hello',
169
+ '',
170
+ 'Body here.',
171
+ '',
213
172
  ].join('\n')
214
173
 
215
- const outnext = (code, msg) => {
216
- switch (code) {
217
- case DENY:
218
- this.logerror('Sending mail failed: ' + msg)
219
- break
220
- case OK:
221
- this.loginfo('mail sent')
222
- next()
223
- break
224
- default:
225
- this.logerror('Unrecognized return code from sending email: ' + msg)
226
- next()
227
- }
228
- }
229
-
230
- outbound.send_email(from, to, contents, outnext)
174
+ outbound.send_email(from, to, contents, (code, msg) => {
175
+ switch (code) {
176
+ case OK:
177
+ plugin.loginfo('queued')
178
+ break
179
+ case DENY:
180
+ plugin.logerror(`queue failed: ${msg}`)
181
+ break
182
+ }
183
+ })
231
184
  ```
232
185
 
233
- The callback on `send_email()` is passed `OK` if the mail is successfully queued, not when it is successfully delivered. To check delivery status, you need to hook `delivered` and `bounce`.
186
+ The callback fires when the mail is **queued**, not delivered hook `delivered` and `bounce` to observe delivery outcomes.
234
187
 
235
- The callback parameter may be omitted if you don't need to handle errors should queueing to disk fail e.g:
188
+ The callback may be omitted if you don't need to handle queue failure:
236
189
 
237
190
  ```js
238
191
  outbound.send_email(from, to, contents)
239
192
  ```
240
193
 
241
- Various options can be passed to `outbound.send_email` like so:
194
+ Options accepted by `send_email(from, to, contents, next, options)`:
242
195
 
243
- ```js
244
- outbound.send_email(from, to, contents, outnext, options)
245
- ```
196
+ | Option | Description |
197
+ | --- | --- |
198
+ | `dot_stuffed: true` | Content is already SMTP dot-stuffed. |
199
+ | `notes: { … }` | Seed the new transaction's `notes`. |
200
+ | `remove_msgid: true` | Drop any existing `Message-Id:` so Haraka generates one. Useful when releasing from quarantine. |
201
+ | `remove_date: true` | Drop any existing `Date:` so Haraka generates one. |
202
+ | `origin: <object>` | Object passed to the logger to identify the source plugin / connection / HMailItem. |
246
203
 
247
- Where `options` is a Object that may contain the following keys:
248
-
249
- | Key/Value | Description |
250
- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
251
- | `dot_stuffed: true` | Use this if you are passing your content dot-stuffed (a dot at the start of a line is doubled, like it is in SMTP conversation, see [RFC 2821][url-rfc2821]. |
252
- | `notes: { key: value}` | In case you need notes in the new transaction that `send_email()` creates. |
253
- | `remove_msgid: true` | Remove any Message-Id header found in the message. If you are reading a message in from the filesystem and you want to ensure that a generated Message-Id header is used in preference over the original. This is useful if you are releasing mail from a quarantine. |
254
- | `remove_date: true` | Remove any Date header found in the message. If you are reading a message in from the filesystem and you want to ensure that a generated Date header is used in preference over the original. This is useful if you are releasing mail from a quarantine. |
255
- | `origin: Object` | Adds object as argument to logger.log calls inside outbound.send_email. Useful for tracking which Plugin/Connection/HMailItem object generated email. |
256
-
257
- ```js
258
- outbound.send_email(from, to, contents, outnext, { notes: transaction.notes })
259
- ```
204
+ To send an already-built `Transaction` directly, use `outbound.send_trans_email(transaction, next)`. This is what `send_email()` calls internally and fires the `pre_send_trans_email` hook.
260
205
 
261
206
  <a name="fn1">1</a>: `Address` objects are [address-rfc2821](https://github.com/haraka/node-address-rfc2821) objects.
262
207
 
263
- [url-tls]: https://haraka.github.io/plugins/tls
208
+ [url-tls]: plugins/tls.md
264
209
  [url-harakamx]: https://github.com/haraka/haraka-net-utils?tab=readme-ov-file#harakamx
265
210
  [url-rfc2821]: https://tools.ietf.org/html/rfc2821#section-4.5.2