@bumail/smtp 0.5.0 → 0.7.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 +22 -2
- package/dist/chunks/{index-n4xwep5m.js → index-5228egp5.js} +52 -49
- package/dist/chunks/{index-n4xwep5m.js.map → index-5228egp5.js.map} +6 -6
- package/dist/client/index.js +23 -7
- package/dist/client/index.js.map +5 -5
- package/dist/client/refusal.d.ts +6 -0
- package/dist/client/refusal.d.ts.map +1 -1
- package/dist/client/socket.d.ts +3 -3
- package/dist/client/socket.d.ts.map +1 -1
- package/dist/client/transaction.d.ts.map +1 -1
- package/dist/errors.d.ts +15 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.js +100 -34
- package/dist/index.js.map +8 -8
- package/dist/server/admission.d.ts +10 -2
- package/dist/server/admission.d.ts.map +1 -1
- package/dist/server/server.d.ts +9 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/settings.d.ts +3 -0
- package/dist/server/settings.d.ts.map +1 -1
- package/dist/server/tls-context.d.ts +36 -6
- package/dist/server/tls-context.d.ts.map +1 -1
- package/docs/guide.md +85 -6
- package/docs/roadmap.md +6 -0
- package/docs/troubleshooting.md +46 -0
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/server/settings.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/server/settings.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnD,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,qDAAqD;AACrD,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,YAAY,CAAC;IACnC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,mFAAmF;IACnF,QAAQ,CAAC,GAAG,CAAC,EAAE,SAAS,CAAC;IACzB,sFAAsF;IACtF,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAC5C,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACpD;AAkED,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,QAAQ,CA6D/D"}
|
|
@@ -1,16 +1,46 @@
|
|
|
1
1
|
import { type SecureContext } from 'node:tls';
|
|
2
2
|
/** `key` or `cert` as `Bun.listen` takes them: text, bytes or a file. */
|
|
3
3
|
type TlsMaterial = string | Uint8Array | Bun.BunFile;
|
|
4
|
+
/** A key and certificate pair, as `Bun.listen` takes it. */
|
|
5
|
+
interface TlsPair {
|
|
6
|
+
readonly key: TlsMaterial;
|
|
7
|
+
readonly cert: TlsMaterial;
|
|
8
|
+
}
|
|
9
|
+
/** `tls` read whole, with the `node:tls` context made from it. */
|
|
10
|
+
interface ReadTls {
|
|
11
|
+
readonly key: string | Buffer;
|
|
12
|
+
readonly cert: string | Buffer;
|
|
13
|
+
readonly context: SecureContext;
|
|
14
|
+
}
|
|
4
15
|
/**
|
|
5
16
|
* `tls` read whole — a file included — and checked as a `node:tls` context:
|
|
6
17
|
* what implicit TLS behind a proxy runs on, and, without a proxy, the check
|
|
7
18
|
* that makes a key or certificate that cannot be read or used fail
|
|
8
|
-
* `listen()` the same way. `fail` turns the reason into the
|
|
9
|
-
* error, the `node:tls` or file error as its `cause`.
|
|
19
|
+
* `listen()` and `setTls()` the same way. `fail` turns the reason into the
|
|
20
|
+
* package's own error, the `node:tls` or file error as its `cause`.
|
|
10
21
|
*/
|
|
11
|
-
export declare function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
22
|
+
export declare function readTls(tls: TlsPair, fail: (message: string, cause: unknown) => Error, caller: string): Promise<ReadTls>;
|
|
23
|
+
/**
|
|
24
|
+
* The key and certificate the server uses now. What `listen()` or `setTls()`
|
|
25
|
+
* read replaces it whole, and only once checked, so a pair that cannot be
|
|
26
|
+
* used leaves the one in use. A STARTTLS server that has read nothing yet
|
|
27
|
+
* serves `tls` as it was given.
|
|
28
|
+
*/
|
|
29
|
+
export declare class TlsHolder {
|
|
30
|
+
#private;
|
|
31
|
+
constructor(given: TlsPair);
|
|
32
|
+
/** What an upgrade to TLS, or a native listener, is given. */
|
|
33
|
+
get options(): TlsPair;
|
|
34
|
+
/** The `node:tls` context of the pair in use: only once `load` or `replace` read one. */
|
|
35
|
+
get context(): SecureContext;
|
|
36
|
+
/** Reads and checks the pair given, unless a read already replaced it. */
|
|
37
|
+
load(fail: (message: string, cause: unknown) => Error, caller: string): Promise<SecureContext>;
|
|
38
|
+
/**
|
|
39
|
+
* Checks `tls`, then uses it from the next connection on. Calls apply in
|
|
40
|
+
* the order they were made, however long each read takes; one that fails
|
|
41
|
+
* does not hold the next.
|
|
42
|
+
*/
|
|
43
|
+
replace(tls: TlsPair, fail: (message: string, cause: unknown) => Error, caller: string): Promise<void>;
|
|
44
|
+
}
|
|
15
45
|
export {};
|
|
16
46
|
//# sourceMappingURL=tls-context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tls-context.d.ts","sourceRoot":"","sources":["../../src/server/tls-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AAEnE,yEAAyE;AACzE,KAAK,WAAW,GAAG,MAAM,GAAG,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC;AAErD
|
|
1
|
+
{"version":3,"file":"tls-context.d.ts","sourceRoot":"","sources":["../../src/server/tls-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AAEnE,yEAAyE;AACzE,KAAK,WAAW,GAAG,MAAM,GAAG,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC;AAErD,4DAA4D;AAC5D,UAAU,OAAO;IAChB,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;CAC3B;AAED,kEAAkE;AAClE,UAAU,OAAO;IAChB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;CAChC;AAED;;;;;;GAMG;AACH,wBAAsB,OAAO,CAC5B,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,KAAK,EAChD,MAAM,EAAE,MAAM,GACZ,OAAO,CAAC,OAAO,CAAC,CAoBlB;AAED;;;;;GAKG;AACH,qBAAa,SAAS;;gBAMT,KAAK,EAAE,OAAO;IAI1B,8DAA8D;IAC9D,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,yFAAyF;IACzF,IAAI,OAAO,IAAI,aAAa,CAG3B;IAED,0EAA0E;IACpE,IAAI,CACT,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,KAAK,EAChD,MAAM,EAAE,MAAM,GACZ,OAAO,CAAC,aAAa,CAAC;IAOzB;;;;OAIG;IACH,OAAO,CACN,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,KAAK,EAChD,MAAM,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC;CAOhB"}
|
package/docs/guide.md
CHANGED
|
@@ -15,6 +15,7 @@ behaviour.
|
|
|
15
15
|
- [Delivering into @bumail/store](#delivering-into-bumailstore)
|
|
16
16
|
- [Authentication](#authentication)
|
|
17
17
|
- [TLS](#tls)
|
|
18
|
+
- [Renewing the certificate without a restart](#renewing-the-certificate-without-a-restart)
|
|
18
19
|
- [Limits](#limits)
|
|
19
20
|
- [Running behind a TCP proxy](#running-behind-a-tcp-proxy)
|
|
20
21
|
- [Sending mail: the client](#sending-mail-the-client)
|
|
@@ -781,9 +782,10 @@ export interface TlsOptions {
|
|
|
781
782
|
}
|
|
782
783
|
```
|
|
783
784
|
|
|
784
|
-
`key` and `cert` are PEM, as `Bun.listen` takes them. Read them
|
|
785
|
+
`key` and `cert` are PEM, as `Bun.listen` takes them. Read them at
|
|
785
786
|
start-up, with `Bun.file().text()`; a missing file then fails before the
|
|
786
|
-
server listens, not at a client's first STARTTLS
|
|
787
|
+
server listens, not at a client's first STARTTLS. A renewed pair is
|
|
788
|
+
applied with `setTls`, [below](#renewing-the-certificate-without-a-restart):
|
|
787
789
|
|
|
788
790
|
```ts
|
|
789
791
|
import { createSmtpServer } from '@bumail/smtp';
|
|
@@ -840,10 +842,13 @@ reads them over TLS and `session.secure` is already `true`. A handshake
|
|
|
840
842
|
that fails is closed by Bun at once and counted out. A STARTTLS handshake
|
|
841
843
|
is bounded by the idle `timeout`, the connection already counted.
|
|
842
844
|
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
845
|
+
The listener of an implicit TLS port is a clear one, and each socket is
|
|
846
|
+
upgraded to TLS (`socket.upgradeTLS`) as it opens, with the pair in use
|
|
847
|
+
at that moment: that is what lets `setTls` apply to the next connection
|
|
848
|
+
(see [below](#renewing-the-certificate-without-a-restart)). The upgraded
|
|
849
|
+
socket calls `open` at the TCP connection, with the socket's timer
|
|
850
|
+
running during the handshake, which is how a stuck handshake is counted
|
|
851
|
+
and closed.
|
|
847
852
|
|
|
848
853
|
Behind a TCP proxy that sends the PROXY protocol, implicit TLS starts after
|
|
849
854
|
the proxy's header, and runs differently: see
|
|
@@ -912,6 +917,67 @@ await createSmtpServer(submission).listen({ port: 587 });
|
|
|
912
917
|
await createSmtpServer({ ...submission, implicitTls: true }).listen({ port: 465 });
|
|
913
918
|
```
|
|
914
919
|
|
|
920
|
+
### Renewing the certificate without a restart
|
|
921
|
+
|
|
922
|
+
A certificate that is renewed — by certbot, by Traefik's dump, by an ACME
|
|
923
|
+
client — should take effect without a restart, and without dropping the
|
|
924
|
+
sessions open. `setTls({ key, cert })` does that:
|
|
925
|
+
|
|
926
|
+
```ts
|
|
927
|
+
const mx = createSmtpServer({ hostname: 'mx.example.com', localDomains: ['example.com'], tls, onData });
|
|
928
|
+
await mx.listen({ port: 25 });
|
|
929
|
+
|
|
930
|
+
// Later, once the files changed:
|
|
931
|
+
await mx.setTls({
|
|
932
|
+
key: await Bun.file('/etc/ssl/mx.example.com.key').text(),
|
|
933
|
+
cert: await Bun.file('/etc/ssl/mx.example.com.crt').text(),
|
|
934
|
+
});
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
- **What changes**: the next STARTTLS upgrade, and on implicit TLS the next
|
|
938
|
+
connection, use the new pair. A session already encrypted keeps the TLS
|
|
939
|
+
it has until it ends, and goes on working; a handshake that began
|
|
940
|
+
before the call may still finish on the old pair.
|
|
941
|
+
- **It is checked first.** `setTls` reads both (a `Bun.file` included) and
|
|
942
|
+
makes a TLS context of them, as `listen()` does on implicit TLS. A pair
|
|
943
|
+
that cannot be read, is not PEM or is a key for another certificate
|
|
944
|
+
rejects with an `SmtpError`, `INVALID_OPTION`, message `setTls(): tls:
|
|
945
|
+
{ key, cert } cannot be used: …`, the reason as its `cause`; the old
|
|
946
|
+
pair stays in use. It does not check that the certificate has not
|
|
947
|
+
expired or names the host: that is the caller's.
|
|
948
|
+
- **One call per server.** The MX on 25, submission on 587 and the
|
|
949
|
+
implicit TLS server on 465 are three servers, each with its own
|
|
950
|
+
`setTls`. Call it on all of them with the same pair.
|
|
951
|
+
- **Calls apply in order.** Two `setTls` in flight, the first reading
|
|
952
|
+
files, end on the second's pair: the later call wins. A key or
|
|
953
|
+
certificate that is empty is refused like any pair that cannot be used.
|
|
954
|
+
- **Before `listen()` too**: the pair set is the one served. A server made
|
|
955
|
+
without `tls` has none to replace: `setTls` rejects with
|
|
956
|
+
`INVALID_OPTION`.
|
|
957
|
+
- **A file read at `setTls`, not at each connection.** Passing a
|
|
958
|
+
`Bun.file` reads it once, when `setTls` runs: to take a later change,
|
|
959
|
+
call `setTls` again.
|
|
960
|
+
|
|
961
|
+
How it works on each kind of port, measured on Bun 1.4.2:
|
|
962
|
+
|
|
963
|
+
- **STARTTLS**: the options of `socket.upgradeTLS` are read for each
|
|
964
|
+
upgrade, so the server swaps the pair it keeps.
|
|
965
|
+
- **Native implicit TLS**: a `Bun.listen` TLS listener keeps the TLS
|
|
966
|
+
context it was created with. `listener.reload()` takes handlers only,
|
|
967
|
+
and a `tls` given to it is ignored (a new connection still got the old
|
|
968
|
+
certificate). The port listens in clear instead, and every socket is
|
|
969
|
+
upgraded with `socket.upgradeTLS` as it opens, with the pair in use
|
|
970
|
+
then, which is the STARTTLS mechanism from the first byte. The
|
|
971
|
+
encrypted bytes also pass through the clear socket's handler, which
|
|
972
|
+
drops them.
|
|
973
|
+
- **Implicit TLS behind `proxyProtocol`** already runs on `node:tls`: the
|
|
974
|
+
server swaps the secure context its new sockets are given.
|
|
975
|
+
- **A second listener on the same port** was not used: `Bun.listen` takes
|
|
976
|
+
`reusePort`, so a new listener could bind beside the old one and the old
|
|
977
|
+
one stop, but both must have opted in from the start, and a
|
|
978
|
+
connection waiting to be accepted by the old one when it stops is reset.
|
|
979
|
+
Upgrading in `open` has neither cost.
|
|
980
|
+
|
|
915
981
|
## Limits
|
|
916
982
|
|
|
917
983
|
| Limit | Default | Past it |
|
|
@@ -1584,6 +1650,19 @@ is not. Every other outcome rejects with an `SmtpError`: its `code`, its
|
|
|
1584
1650
|
`temporary`, and, when a reply caused it, `reply` with its enhanced status
|
|
1585
1651
|
code (RFC 3463).
|
|
1586
1652
|
|
|
1653
|
+
`afterData` is `true` when the final dot was written and no reply to it came
|
|
1654
|
+
(a `TIMEOUT`, a `CONNECTION_LOST` or a `BAD_REPLY`): the server may already
|
|
1655
|
+
have taken the message (RFC 5321 §4.1.5), so sending it again, or by another
|
|
1656
|
+
route, can duplicate it. It is absent for a failure before the dot, which
|
|
1657
|
+
the server drops, and for a `REFUSED` to the dot, a 4xx or 5xx that says the
|
|
1658
|
+
message was not taken.
|
|
1659
|
+
|
|
1660
|
+
```ts
|
|
1661
|
+
if (error.temporary && error.afterData) {
|
|
1662
|
+
// the message may be there already: retry the same host, not another route
|
|
1663
|
+
}
|
|
1664
|
+
```
|
|
1665
|
+
|
|
1587
1666
|
| `code` | when | `temporary` |
|
|
1588
1667
|
| --- | --- | --- |
|
|
1589
1668
|
| `INVALID_OPTION` | an option, an address or the message cannot be used | no |
|
package/docs/roadmap.md
CHANGED
|
@@ -34,6 +34,12 @@ Nothing in progress.
|
|
|
34
34
|
|
|
35
35
|
### Unreleased — merged, not yet published
|
|
36
36
|
|
|
37
|
+
- **A renewed certificate without a restart**, `server.setTls({ key, cert
|
|
38
|
+
})`: the next STARTTLS upgrade and the next implicit TLS connection use
|
|
39
|
+
it, the sessions open keep their TLS, and a pair that cannot be used
|
|
40
|
+
throws `INVALID_OPTION` and leaves the old one. Implicit TLS without a
|
|
41
|
+
proxy now listens in clear and upgrades each socket as it opens, since
|
|
42
|
+
a native Bun TLS listener cannot swap its certificate.
|
|
37
43
|
- **Behind a TCP proxy, the client's own address**, with `proxyProtocol:
|
|
38
44
|
{ trusted: [...] }`: the server reads the PROXY protocol, versions 1 and
|
|
39
45
|
2, from the proxies listed — a Traefik TCP router, HAProxy — so
|
package/docs/troubleshooting.md
CHANGED
|
@@ -32,6 +32,8 @@ A reply carries its enhanced status code (`5.7.1`, RFC 3463) only after
|
|
|
32
32
|
- [`SmtpError: listen(): the server is already listening on …`](#smtperror-listen-the-server-is-already-listening-on-)
|
|
33
33
|
- [`SmtpError: listen(): the server is already starting to listen`](#smtperror-listen-the-server-is-already-starting-to-listen)
|
|
34
34
|
- [`SmtpError: listen(): tls: { key, cert } cannot be used: …`](#smtperror-listen-tls--key-cert--cannot-be-used-)
|
|
35
|
+
- [`SmtpError: setTls(): tls: { key, cert } cannot be used: …`](#smtperror-settls-tls--key-cert--cannot-be-used-)
|
|
36
|
+
- [`SmtpError: setTls(): the server was made without tls, so it has none to replace`](#smtperror-settls-the-server-was-made-without-tls-so-it-has-none-to-replace)
|
|
35
37
|
- [`SmtpError: listen(): stop() was called before the server bound its port`](#smtperror-listen-stop-was-called-before-the-server-bound-its-port)
|
|
36
38
|
|
|
37
39
|
**Relaying and authentication**
|
|
@@ -625,6 +627,50 @@ try {
|
|
|
625
627
|
}
|
|
626
628
|
```
|
|
627
629
|
|
|
630
|
+
### `SmtpError: setTls(): tls: { key, cert } cannot be used: …`
|
|
631
|
+
|
|
632
|
+
**When**: `setTls` was given a renewed pair whose `key` or `cert` cannot
|
|
633
|
+
be read — a `Bun.file` that does not exist, or one caught half-written —
|
|
634
|
+
or is empty (`… the key is empty`, `… the cert is empty`), or is not a
|
|
635
|
+
PEM key or certificate, or a key that is not the certificate's. Its `code` is `INVALID_OPTION`; the rest of the message,
|
|
636
|
+
and its `cause`, are the reason from the file system or `node:tls`
|
|
637
|
+
(`ENOENT: no such file or directory, open '…'`, `…PEM routines…`,
|
|
638
|
+
`key values mismatch`). The pair in use stays, for new connections too.
|
|
639
|
+
|
|
640
|
+
**Why**: `setTls` reads both and checks them as a TLS context before it
|
|
641
|
+
swaps anything, so a bad pair never reaches a client.
|
|
642
|
+
|
|
643
|
+
**Fix**: write both files before calling it — a renewal that writes the key
|
|
644
|
+
first and the certificate after fails here, which is the right answer —
|
|
645
|
+
and retry:
|
|
646
|
+
|
|
647
|
+
```ts
|
|
648
|
+
try {
|
|
649
|
+
await server.setTls({
|
|
650
|
+
key: Bun.file('/etc/bumail/tls/privkey.pem'),
|
|
651
|
+
cert: Bun.file('/etc/bumail/tls/fullchain.pem'),
|
|
652
|
+
});
|
|
653
|
+
} catch (error) {
|
|
654
|
+
console.error(error, (error as Error).cause); // the old pair is still served
|
|
655
|
+
}
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
### `SmtpError: setTls(): the server was made without tls, so it has none to replace`
|
|
659
|
+
|
|
660
|
+
**When**: `setTls` on a server created without the `tls` option. Its `code`
|
|
661
|
+
is `INVALID_OPTION`.
|
|
662
|
+
|
|
663
|
+
**Why**: TLS is what turns on STARTTLS, AUTH and implicit TLS, and each
|
|
664
|
+
changes what the server says: `setTls` replaces a pair, it does not turn
|
|
665
|
+
TLS on.
|
|
666
|
+
|
|
667
|
+
**Fix**: create the server with `tls`, the pair it starts with:
|
|
668
|
+
|
|
669
|
+
```ts
|
|
670
|
+
const server = createSmtpServer({ ...options, tls: { key, cert } });
|
|
671
|
+
await server.setTls({ key: renewedKey, cert: renewedCert });
|
|
672
|
+
```
|
|
673
|
+
|
|
628
674
|
## Relaying and authentication
|
|
629
675
|
|
|
630
676
|
### `554 5.7.1 Relay access denied`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bumail/smtp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "An SMTP server on Bun.listen — RFC 5321 with PIPELINING, SIZE, 8BITMIME, SMTPUTF8, STARTTLS and AUTH after TLS, policy hooks, and no open relay — and a client that delivers a message to a smarthost or by MX",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -47,9 +47,9 @@
|
|
|
47
47
|
]
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@bumail/auth": "^0.
|
|
51
|
-
"@bumail/dns": "^0.
|
|
52
|
-
"@bumail/store": "^0.4.
|
|
50
|
+
"@bumail/auth": "^0.4.1",
|
|
51
|
+
"@bumail/dns": "^0.2.1",
|
|
52
|
+
"@bumail/store": "^0.4.1",
|
|
53
53
|
"@types/bun": "^1.4.2"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|