I'm trying to send emails with Nodemailer containing attachments hosted in S3, using JS AWS SDK v3. The Nodemailer docs show an example of sending an attachment using a read stream generated from reading a file:
{ // stream as an attachment
filename: 'text4.txt',
content: fs.createReadStream('file.txt')
},
I can create a ReadableStream
for my attachment in S3 like so:
const s3Stream = await s3Client
.send(/* ... */)
.then((response) => {
// ...
return response.Body.transformToWebStream() // returns Promise<ReadableStream>
})
.catch(/* ... */)
If I attempt to pass s3Stream
directly into the content
field for my Nodemailer attachment, I get an error: Type 'ReadableStream' is not assignable to type 'string | Readable | Buffer | undefined
.
The Readable
class has a static method fromWeb
. Calling Readable.fromWeb(s3Stream)
gives the following:
Argument of type 'ReadableStream' is not assignable to parameter of type 'ReadableStream<any>'.
Type 'ReadableStream' is missing the following properties from type 'ReadableStream<any>': locked, cancel, getReader, pipeThrough, and 4 more.
Readable
objects have a method wrap
:
new Readable().wrap(s3Stream)
Trying this results in the following error:
Argument of type 'ReadableStream' is not assignable to parameter of type 'NodeJS.ReadableStream'.
Type 'ReadableStream' is missing the following properties from type 'ReadableStream': readable, read, setEncoding, pause, and 22 more.
That's a known problem with S3 SDK (check out this GitHub issue), it shows response.Body
as a union of typings for both browsers (ReadableStream
) and node.js (Readable
), while at the runtime returning a determined type of value depending on the platform you run it on.
So if you are sure that your code will only work in node.js you just do type casting:
import { Readable } from 'node:stream';
(res.Body as Readable)?.pipe(...);
Or even better add a type guard just to be sure:
import { Readable } from 'node:stream';
if(isReadable(res.Body)) {
res.Body.pipe(fs.createWriteStream('./file2.txt'));
} else {
throw new TypeError('res.Body is not a readable stream');
}
function isReadable(value: unknown): value is Readable {
return value instanceof Readable
}