Hey guys I have a question regarding the fetch function from JavaScript and the Package Nodemailer.
NOTE: I am using Expo.
I have a PDF created on my Emulator which looks good (filled with data). Now I want to send this PDF to my Backend and therer I want to send the File out as a PDF and get it in my Mails.
Frontend fetch-Call for my Backend:
async sendMonthReportMail(fileUri) {
await fetch(`${connectionString}/api/mails/sendmonthreport`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
senderUser: "My Name",
recieverMail: "[email protected]",
pathMonthLock: `${fileUri}`
})
})
.then(res => res.json())
.then(res => {
console.log("Success SendMailMonthLock - " res)
})
.catch(err => console.log("Error SendMailMonthLock - " err))
}
I have also tried to use formData and change the Content-Type to "application/pdf" but this did not work.
Now my Backend Code looks like this:
router.post("/sendmonthreport", async (req, res) => {
const {recieverMail} = req.body;
const {senderUser} = req.body;
const {pathMonthLock} = req.body;
let transport = nodemailer.createTransport({
host: "mailserver",
port: xx,
secure: false,
tls: {
rejectUnauthorized: false
}
});
var message = {
from: "My Sendermail <[email protected]>",
to: `${recieverMail}`,
subject: `Mail comming from - ${senderUser}`,
text: `EmailText `,
attachments:[{
filename: "MyFilename.pdf",
content: __dirname `./${pathMonthLock}`,
}]
}
await transport.sendMail(message)
.catch(err => console.log("Error Mail: " err ))
res.send("email send")
})
Now the way that I choose the file.uri is threw the DocumentPicker for Expo.
The Problem I am having now is, that the Mail gets send correctly and there is a PDF attached to it with the correct name. However when I look at the PDF Size it is only 500Bytes and it is completly empty. When trying to open the File that is send via Mail then it tells me that the File is corrupted or wrong format.
How can I send the actual content that is inside the PDF?
When I try to use formData:
async sendMonthReportMail(file) {
fdPost.append("file", file);
fdPost.append("senderUser", "User Name");
fdPost.append("recieverMail", "[email protected]");
console.log(fdPost);
await fetch(`${connectionString}/api/mails/sendmonthreport`, {
method: "POST",
headers: {
accept: "application/json"
// "Content-Type": "multipart/form-data",
// "Content-Type": "application/json",
// "Content-Type" : "application/pdf"
// "Content-Type": "application/x-www-form-urlencoded"
},
body: fdPost
})
.then(res => res.json())
.then(res => {
console.log("Success SendMailMonthLock - " res)
})
.catch(err => console.log("Error SendMailMonthLock - " err))
}
fdPost is defined at the top of my file, console.logging it gives me a valid FormData but the request ends with a Network Error!
Error SendMailMonthLock - TypeError: Network request failed
Also my Backend does not react to that call.
I also tried to use RN Fetch Blob but it only works in React Native only Projects. I am using Expo so this is not a soultion for me!
Edit:
I tried the suggestion in the comments (using Multer):
Frontend:
async sendMonthReportMail(file) {
fdPost.append("file", file);
fdPost.append("senderUser", "name");
fdPost.append("recieverMail", "[email protected]");
await fetch(`${connectionString}/api/mails/sendmonthreportTest`, {
method: "POST",
headers: {
Accept: "application/json",
'Content-Type': 'multipart/form-data'
},
body: fdPost
})
.catch(err => console.log("Error SendMailMonthLock - " err))
}
Backend:
router.post("/sendmonthreportTest", upload.single("file"), (req, res) => {
console.dir(req.file req.body);
})
Still I have the same issue! It gives me TypeError: Network Error.
Also when I try to call that point with Postman I am getting:
POST /api/mails/sendmonthreportTest 500 6.952 ms - 1221
TypeError: Cannot convert object to primitive value
To confirm my POST Body when I console.log the fdPost before the fetch:
{"_parts":[["file",{"size":69496,"name":"Monatsbericht_User_Name_Dezember2022.pdf","type":"success","uri":"file:///data/user/0/host.exp.exponent/cache/ExperienceData/%40yxxx%2Fxxx/DocumentPicker/2xx07e4-4136-4b6f-ad70-26xxac64064.pdf","mimeType":"application/pdf"}],["senderUser","User Name"],["recieverMail","[email protected]"]]}
CodePudding user response:
It seems like you are trying to send the file URI of the PDF instead of the actual file content to your backend. To send the actual content of the file, you can use the react-native-fs package to read the file and convert it to base64. Then you can send the base64 encoded file in the body of your fetch request.
You can use the following code to read the file and convert it to base64:
import RNFS from 'react-native-fs';
async sendMonthReportMail(fileUri) {
let file = await RNFS.readFile(fileUri, 'base64');
await fetch(`${connectionString}/api/mails/sendmonthreport`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
senderUser: "My Name",
recieverMail: "[email protected]",
file: file
})
})
.then(res => res.json())
.then(res => {
console.log("Success SendMailMonthLock - " res)
})
.catch(err => console.log("Error SendMailMonthLock - " err))
}
On the backend, you can use the Buffer class to convert the base64 encoded file back to a binary format and then attach it to the email.
router.post("/sendmonthreport", async (req, res) => {
const {recieverMail} = req.body;
const {senderUser} = req.body;
const {file} = req.body;
let transport = nodemailer.createTransport({
host: "mailserver",
port: xx,
secure: false,
tls: {
rejectUnauthorized: false
}
});
var message = {
from: "My Sendermail <[email protected]>",
to: `${recieverMail}`,
subject: `Mail comming from - ${senderUser}`,
text: `EmailText `,
attachments:[{
filename: "MyFilename.pdf",
content: new Buffer(file, 'base64')
}]
}
await transport.sendMail(message)
.catch(err => console.log("Error Mail: " err ))
res.send("email send")
})
Also you have to check for file size limit and also file type, because some email services may reject large attachments or certain file types.
CodePudding user response:
UPDATE: I provided sample code that utilizes the npm package nodemailer to send an email with an attachment. The Attachment class is a part of the nodemailer library and is used to attach a file to the email that is being sent.
Regarding the issue with fetchBlob not being an option and the issue with using multer, it's possible that the issue is with the way the file is being passed to the function that is sending the email. Without more information about the specific code and error messages, it's difficult to provide a specific solution.
One suggestion is to make sure that the file being passed as an attachment is a valid file that exists in the specified path. You can also try to use fs module to read the file and then pass it as a stream to the attachment.
let fs = require('fs');
let attachment = new Attachment(fs.createReadStream(pathMonthLock));
It may also be helpful to check for any errors that are being thrown when trying to attach the file and log them for further investigation.
It looks like you're running into issues with sending the actual contents of the PDF file to your backend and then sending it as an attachment in the email.
One issue in your frontend code is that when using fetch, you're sending the file URI in the body as a string, instead of the actual file contents. In order to send the actual file contents, you'll need to use a library like fetch-blob or react-native-fetch-blob (if you're using React Native) to read the file contents from the file URI and then send that in the body of the request.
In your backend code, you're using __dirname ./${pathMonthLock}`, to read the file. But since you are sending the fileUri from frontend, It should be read from the fileUri instead of __dirname.
Also you are using nodemailer library to send the email. Instead of reading the file from the path, you can use nodemailer-attachments which can be used to send the file as an attachment.
const attachment = new Attachment(pathMonthLock);
var message = {
from: "My Sendermail <[email protected]>",
to: `${recieverMail}`,
subject: `Mail comming from - ${senderUser}`,
text: `EmailText `,
attachments:[attachment]
}
It is better to use the multer or multer-s3 for handling the file uploads in the backend.
Also, it is important to check if the file passed from frontend is of correct format and not corrupted.