First of all, thank you for the previous quick fix!
Version: 0.5.1-beta (commit 571934d)
Summary
When a node has no archiveType configured, EchoMail/NetMail export falls through to moveFilesToOutgoing()'s direct-attach rename path, which produces a BSO flow-file name with the extension concatenated directly onto the basename — no separating dot (e.g. 43792ae5cut instead of 43792ae5.cut). Files created this way are invisible to the native BinkP mailer: core/binkp/bso_spool.js matches outbound files with the anchored regex
/^([0-9a-f]{8})\.(flo|clo|ilo|hlo|dlo|out|cut|iut|hut|dut)$/i
which requires the dot. The message sits in the outbound spool indefinitely, is skipped on every scheduled pull and crashmail dispatch, and no error is logged at any log level — the writer's fse.move() succeeds; it just moves the file to the wrong name.
Root cause — core/scanner_tossers/ftn_bso.js, moveFilesToOutgoing(), line 1378:
if ('.pk_' === ext.toLowerCase()) {
const newExt = self.getOutgoingFlowFileExtension(
exportOpts.destAddress, 'mail', exportType, exportOpts.fileCase
); // returns bare extension text, e.g. "cut" — no leading dot,
// see getOutgoingFlowFileExtension(), line 230
const newPath = paths.join(
outgoingDir,
`${paths.basename(oldPath, ext)}${newExt}` // <-- missing dot
);
fse.move(oldPath, newPath, nextFile);
}
getOutgoingFlowFileExtension() intentionally returns the extension without a leading dot — consistent with its other call site in getOutgoingFlowFileName(), which correctly does ${controlFileBaseName}.${ext} a few lines earlier (line ~299). This call site is missing that same . separator.
Compounding issue — malformed name gets misdecoded as a different destination if manually "fixed"
The basename left over from the .pk_ temp file is a message serial number (getMessageSerialNumber(), used for the original packet name at line 209), not a net/node pair. bso_spool.js's getNodesWithPendingMail() assumes any 8-hex-char BSO basename is NNNNnnnn (4 hex digits net + 4 hex digits node). So simply adding the missing dot back in is not a safe fix — it makes the file match the scan regex, but the reader then decodes the serial number as if it were an address, producing a phantom destination (in our repro: basename 43792ae5 → decoded as net 0x4379 / node 0x2ae5 → logged as 700:17273/10981, "No host configured, skipping"). The message is silently misrouted to a non-existent node instead of just failing to send.
Suggested fix
Insert the missing dot at line 1378:
${paths.basename(oldPath, ext)}.${newExt}
This alone fixes the immediate bug, but note the correct fix for a fully-working direct-attach path also requires generating a real net/node-based basename (as getOutgoingFlowFileName() does) rather than reusing the .pk_ temp file's serial-number name — otherwise multiple unbundled messages queued to the same node before one ships would collide on identical filenames.
Reproduction (confirmed via packet capture)
Configure two or more FTN networks with a message area exporting to a node that has no archiveType configured.
Post/toss a message to that area, destined for that node.
Observe the resulting file in mail/ftn_out// — correct 8-hex-char serial number, but no dot before the extension (e.g. 43792ae5cut).
tcpdump a subsequent BinkP session to that node: no M_FILE frame is ever sent for it. QSIZE/session completes normally otherwise — no error anywhere in logs at any log level, including trace.
As a manual recovery test, renaming the file to insert the dot (43792ae5.cut) causes the next session to attempt sending it — but to a decoded address of 700:17273/10981 rather than the real destination, confirmed via "[BinkP/Caller] No host configured, skipping" in the log, with the hex math on the basename (4379/2ae5) matching exactly.
Renaming to the correct net/node basename for the intended destination (00640000.cut for 700:100/0) ships successfully — confirmed via M_GOT from the remote hub and file removal from the spool afterward.
Workaround
Setting archiveType (e.g. ZIP, assuming InfoZip's zip/unzip are installed) on the affected node in scannerTossers.ftn_bso.nodes routes export through createArcMailBundle() instead, entirely avoiding the buggy direct-attach rename path. Confirmed working — subsequent EchoMail exports crashmail immediately with no leftover file in the outbound spool.
Impact
Silent, permanent mail non-deliveryfor any message exported to a node without archiveType configured — which is the natural default for a newly-added FTN peer, since archiveType isn't required by any startup validation and the example config in the docs shows it as present but doesn't flag its absence as a problem.
Environment
- [X ] I am using Node.js v14.x LTS or higher
- [ X]
npm install or yarn reports success
- Actual Node.js version (
node --version):v22.23.2
- Operating system (
uname -a on *nix systems):Linux bbs 7.0.14-4-pve #1 SMP PREEMPT_DYNAMIC PMX 7.0.14-4 (2026-07-07T07:27Z) x86_64 GNU/Linux
- Revision (
git rev-parse --short HEAD):571934db
First of all, thank you for the previous quick fix!
Version: 0.5.1-beta (commit 571934d)
Summary
When a node has no archiveType configured, EchoMail/NetMail export falls through to moveFilesToOutgoing()'s direct-attach rename path, which produces a BSO flow-file name with the extension concatenated directly onto the basename — no separating dot (e.g. 43792ae5cut instead of 43792ae5.cut). Files created this way are invisible to the native BinkP mailer: core/binkp/bso_spool.js matches outbound files with the anchored regex
/^([0-9a-f]{8})\.(flo|clo|ilo|hlo|dlo|out|cut|iut|hut|dut)$/iwhich requires the dot. The message sits in the outbound spool indefinitely, is skipped on every scheduled pull and crashmail dispatch, and no error is logged at any log level — the writer's fse.move() succeeds; it just moves the file to the wrong name.
Root cause — core/scanner_tossers/ftn_bso.js, moveFilesToOutgoing(), line 1378:
getOutgoingFlowFileExtension() intentionally returns the extension without a leading dot — consistent with its other call site in getOutgoingFlowFileName(), which correctly does
${controlFileBaseName}.${ext}a few lines earlier (line ~299). This call site is missing that same . separator.Compounding issue — malformed name gets misdecoded as a different destination if manually "fixed"
The basename left over from the .pk_ temp file is a message serial number (getMessageSerialNumber(), used for the original packet name at line 209), not a net/node pair. bso_spool.js's getNodesWithPendingMail() assumes any 8-hex-char BSO basename is NNNNnnnn (4 hex digits net + 4 hex digits node). So simply adding the missing dot back in is not a safe fix — it makes the file match the scan regex, but the reader then decodes the serial number as if it were an address, producing a phantom destination (in our repro: basename 43792ae5 → decoded as net 0x4379 / node 0x2ae5 → logged as 700:17273/10981, "No host configured, skipping"). The message is silently misrouted to a non-existent node instead of just failing to send.
Suggested fix
Insert the missing dot at line 1378:
${paths.basename(oldPath, ext)}.${newExt}This alone fixes the immediate bug, but note the correct fix for a fully-working direct-attach path also requires generating a real net/node-based basename (as getOutgoingFlowFileName() does) rather than reusing the .pk_ temp file's serial-number name — otherwise multiple unbundled messages queued to the same node before one ships would collide on identical filenames.
Reproduction (confirmed via packet capture)
Configure two or more FTN networks with a message area exporting to a node that has no archiveType configured.
Post/toss a message to that area, destined for that node.
Observe the resulting file in mail/ftn_out// — correct 8-hex-char serial number, but no dot before the extension (e.g. 43792ae5cut).
tcpdump a subsequent BinkP session to that node: no M_FILE frame is ever sent for it. QSIZE/session completes normally otherwise — no error anywhere in logs at any log level, including trace.
As a manual recovery test, renaming the file to insert the dot (43792ae5.cut) causes the next session to attempt sending it — but to a decoded address of 700:17273/10981 rather than the real destination, confirmed via "[BinkP/Caller] No host configured, skipping" in the log, with the hex math on the basename (4379/2ae5) matching exactly.
Renaming to the correct net/node basename for the intended destination (00640000.cut for 700:100/0) ships successfully — confirmed via M_GOT from the remote hub and file removal from the spool afterward.
Workaround
Setting archiveType (e.g. ZIP, assuming InfoZip's zip/unzip are installed) on the affected node in scannerTossers.ftn_bso.nodes routes export through createArcMailBundle() instead, entirely avoiding the buggy direct-attach rename path. Confirmed working — subsequent EchoMail exports crashmail immediately with no leftover file in the outbound spool.
Impact
Silent, permanent mail non-deliveryfor any message exported to a node without archiveType configured — which is the natural default for a newly-added FTN peer, since archiveType isn't required by any startup validation and the example config in the docs shows it as present but doesn't flag its absence as a problem.
Environment
npm installoryarnreports successnode --version):v22.23.2uname -aon *nix systems):Linux bbs 7.0.14-4-pve #1 SMP PREEMPT_DYNAMIC PMX 7.0.14-4 (2026-07-07T07:27Z) x86_64 GNU/Linuxgit rev-parse --short HEAD):571934db