My website careers page, User need to send their profile and then profile(doc or pdf) need to send as an attachment to the email on clicking submit button. Technologies I am using Angular6, NodeJs, Express, and Nodemailer to send the email.
Here is my peace of HTML code(careers.html),
<form (ngSubmit)="sendData()" >
<textarea [(ngModel)]="user.message"></textarea>
<input type="file" (change)="userProfile($event)" >
<small>Upload .docx or pdf file</small>
<button >Submit</button>
</form>
a bit of ts code (careers.ts),
user = {
message: '',
fileContent :''
};
userProfile(event: any) {
if (event.target.files && event.target.files[0]) {
var reader = new FileReader();
reader.onload = (event: any) => {
this.user.fileContent = event.target.result;
}
reader.readAsDataURL(event.target.files[0]);
}
sendData() {
this.careersService.sendWithAttachment(this.user);
}
here is service class(service.ts),
sendWithAttachment(userData) {
this.http.post("http://localhost:3000/uploadfile", userData
)
.subscribe(
data => {
console.log("Sent Request is successful ", data);
},
error => {
console.log("Error", error);
}
);
}
finally js file (app.js)
var mailOptions = {
from: '"User" <mail#gmail.com>', // sender address
to: "mail#mail.com", // list of receivers
subject: "Mail from Careers",
text: text,
html: html, // html body
attachments: [ {
filename: 'profile.pdf',
content: req.body.fileContent,
contentType: 'application/pdf'
}]
};
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
return console.log(error);
}
console.log('Message sent: ' + info.response);
});
Now I am able to receive email with attachement but that attachement is empty or corrupted. Any help is much appreciated.
You need to upload the file on the server, Maybe you can do it though multer and then you can pass the server file path to the Nodemailer Function.
Here are some code snippet for your reference,
var express = require('express')
var multer = require('multer')
var upload = multer({ dest: 'uploads/' })
var app = express()
app.post('/uploadfile', upload.single('profile'), function (req, res) {
var mailOptions = {
....
attachments: [{
filename: req.file.filename,
path: req.file.path
}]
};
...
})
Note : Attachment can be done in various methods in Nodemailer, Refer the documentation
Related
We're facing an issue while sending an attachment to a mail. Our scenario is something like, we're having a button "Send to My Email" which collects the logged-in user email and sends the filled form as a pdf to an email attachment. Everything is working fine on our local machine, but we're getting an error on the live testing site.
Our backend expressjs controller code:
const sendFormToEmail = async (req, res, next) => {
try {
const { formDetails } = req.body;
const to = formDetails?.email;
const from = "Our App Name here";
const subject = `Form Attachment`;
const html = emailTemplate({ formDetails });
let options = { format: "A4" };
let file = { content: html };
const pdfBuffer = await html_to_pdf.generatePdf(file, options);
await sendEmail({
to,
subject,
from,
html,
pdfBuffer,
filename: "form.pdf",
});
res.status(200).send({
status: true,
message: "Form sent to email successfully",
});
} catch (err) {
console.log("Error is ", err);
res.status(500).send({
status: false,
message: "Failed to send Form on an email",
});
}
};
sendEmail function code:
async function sendEmail({
to,
from,
subject,
text,
html,
pdfBuffer,
filename,
}) {
try {
const sendEmailResponse = await SENDGRID.send({
from,
to,
text,
html,
subject,
attachments: [
{
content: pdfBuffer.toString("base64"),
filename: filename,
type: "application/pdf",
disposition: "attachment",
},
],
});
console.log("EMAIL_SUCCESSFULLY_SEND: ", sendEmailResponse);
return true;
} catch (err) {
console.log(err.response.body.errors);
throw err;
}
}
The error we're facing:
Error is Error: Failed to launch the browser process!
/root/application-name/node_modules/html-pdf-node/node_modules/puppeteer/.local-chromium/linux-901912/chrome-linux/chrome: error while loading shared libraries: libatk-1.0.so.0: cannot open shared object file: No such file or directory
Please, help us in getting rid of this issue
So, basically, I found the solution for this here.
== updated question on 9/9 ===
Tried to use Multer directly without the Middleware like before and using Postman to upload the images.
From Nodejs, req return
files: [Object: null prototype] {
imagebackup: [ [Object] ],
imagebanner: [ [Object] ]
},
However, when I console req.file
it showing "undefined"
new file-routers.js as below:
const express = require('express');
const multer = require('multer');
const router = express.Router();
const upload = multer({
storage: multer.MemoryStorage,
}).fields([{name: "imagebackup"}, {name: "imagebanner"}]);
router.post('/file', (req, res)=>{
upload(req, res, (err) => {
console.log(req) // return Files [object null]
console.log(req.file) // return "undefined"
if(err) throw err;
})
});
**Weird thing is, by using upload.single(), everything works just fine. **
==
==
===== Here is the old code & can't solve it =====
It return an error
MulterError: Unexpected field
at wrappedFileFilter (C:\Users\carchaw\Documents\pfx_template_generator_api\node_modules\multer\index.js:40:19)
at Busboy.<anonymous> (C:\Users\carchaw\Documents\pfx_template_generator_api\node_modules\multer\lib\make-middleware.js:114:7)
at Busboy.emit (node:events:379:20)
On the form submit, I need upload 2 images from different input field, create new prefix on GCS, and also store the image's name and other's details to be sent in request.body.
From the front-end part, I using Fetch as below:
const getFormContianer = document.getElementById("get_form")
async function handleForm(e) {
e.preventDefault();
let dataForm = new FormData(e.target)
await fetch(file_api, {
method: 'POST',
body: dataForm
}).then((res)=>{
return res.json();
}).then((data)=>{
console.log('api err: '+data);
}).catch((err) =>{
console.log('api err: '+ err)
})
}
getFormContianer.addEventListener('submit', handleForm)
index.html
<form id="get_form">
<label for="video_url">video_url</label>
<input name="video_url" type="text" id="video_url" value=""><br>
<label for="image_backup">image_backup</label>
<input name="image_backup" type="file" id="image_backup" value=""><br>
<label for="image_banner">image_banner</label>
<input name="image_banner" type="file" id="image_banner" value=""><br>
</form>
<input type="submit" id="handle_submit">
Nodejs
multer middleware
const util = require("util");
const multer = require("multer");
let processFile = multer({
storage: multer.memoryStorage()
}).fields([{ name: "image_backup" }, { name: "image_banner" }])
let processFileMiddleware = util.promisify(processFile);
module.exports = processFileMiddleware;
handling Upload
const handleUploadImages = async (req, res) =>{
try {
await processFile(req, res);
if (!req.file) {
return res.status(400).send({ message: "Please upload a file!" });
}
// Create a new blob in the bucket and upload the file data.
const blob = bucket.file(newFolderPath + req.file.originalname);
const blobStream = blob.createWriteStream({
resumable: false,
});
blobStream.on("error", (err) => {
res.status(500).send({ message: err.message });
});
blobStream.on("finish", async (data) => {
// Create URL for directly file access via HTTP.
const publicUrl = format(
`https://storage.googleapis.com/${bucket.name}/${newFolderPath}/${blob.name}`
);
try {
// Make the file public
await bucket.file(newFolderPath + req.file.originalname).makePublic();
} catch {
return res.status(500).send({
message:
`Uploaded the file successfully: ${newFolderPath + req.file.originalname}, but public access is denied!`,
url: publicUrl,
});
}
res.status(200).send({
message: "Uploaded the file successfully: " + newFolderPath + req.file.originalname,
url: publicUrl,
});
});
blobStream.end(req.file.buffer);
} catch (err) {
res.status(500).send({
message: `Could not upload the file: ${req.file.originalname}. ${err}`,
});
}
}
I did use express json and urlencoded on index.js
const express = require('express');
const cors = require('cors');
const config = require('./config')
const app = express()
const templates = require('./routes/templates-routes');
const files = require('./routes/files-routes');
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static('public'))
app.use('/api', templates.routes);
app.use('/create', files.routes);
app.listen(config.port, () => {
console.log(`Example app listening at http://localhost:${config.port}`)
})
Hope that can get some suggestion on this, thank you!
Where is body: dataForm declared? That error arises when you try to upload a field that is not mentioned in fields:
fields([{ name: "image_backup" }, { name: "image_banner" }])
Make sure your multipart form has these 2 fields only for uploading files.
I suggest you to check this post in which they discuss the same issue.
In order to solve it they basically formatted the file that need to be uploaded.
formData.append("type_of_the_file", uploadfile);
Finally solved the issue after few days keep trying.
for front-end POST rest API, passing files by appending file itself and the name.
function handleForm(e) {
e.preventDefault();
let dataForm = new FormData(e.target)
dataForm.append("image_backup", document.getElementById("image_backup").files[0]);
dataForm.append("image_banner", document.getElementById("image_banner").files[0]);
dataForm.append("image_banner_name", document.getElementById("image_banner").value.replace(/^.*[\\\/]/, ''));
dataForm.append("image_backup_name", document.getElementById("image_backup").value.replace(/^.*[\\\/]/, ''));
await fetch(file_api, {
method: 'POST',
body: dataForm
}).then((res)=>{
return res.json();
}).then((data)=>{
console.log('api data: '+ data);
}).catch((err) =>{
console.log('api err: '+ err)
})
}
on Nodejs
const multer = require('multer');
const upload = multer({
storage: multer.MemoryStorage,
}).fields([{name: "image_backup"}, {name: "image_banner"}]);
const startCreatefiles = async(req, res, next) =>{
upload(req, res, (err) => {
console.log(req.body);
console.log(req.files);
})
}
Then successfully get the text form data and file itself.
I have a nestjs server and I have uploaded the images but when I try to access to those images they are a bunch of stuff that cant be interpreted as an image.(I also tried converting them to blob which then I converted to objectURL and then set it as src for img tag but that didnt work either).Upload code:
#Post('upload')
#UseInterceptors(FileInterceptor('file',{
storage: diskStorage({
destination: './uploads',
filename: editFileName,
}),
fileFilter: imageFileFilter,
}))
uploadFile(#UploadedFile() file){
console.log(file);
file.filename = file.originalname;
const response = {
originalname: file.originalname,
filename: file.filename,
}
return response;
}
The above upload code perfectly saves the image as index-53a2.jpg in my uploads folder. Now trying to get the image using get req by:
#Get()
display(#Res() res){
res.sendFile('index-53a2.jpg',{ root: './uploads' })
}
logging response for this it gives some string of unreadable(probably encoded) stuff.
code I used for testing:
<html>
<head>
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script>
$(function () {
$('#abc').on('submit', function (e) {
e.preventDefault();
$.ajax({
url: 'http:/localhost:3000/student/upload',
method:'POST',
data: new FormData(this),
contentType: false,
cache:false,
processData:false,
success: function (data) {
console.log(data);
// location.reload();
}
});
});
});
function fun(){
$.ajax({
url: 'http://localhost:3000/student',
success: function(data){
console.log('s',data);
let blob = new Blob([data]);
var objectURL = URL.createObjectURL(blob);
document.getElementById('img').src = objectURL;
},
error: function(data){
console.log('e',data);
}
})
}
</script>
</head>
<body>
<img alt="Image" id="img">
<form enctype= "multipart/form-data" id="abc">
<input type="file" name="file" required accept="image/*"><br>
<input name="submit" type="submit" value="Submit">
</form>
<button onclick="fun()">Button</button>
</body>
</html>
Also this html code is just for testing, my main purpose is to use this server so that I can take student image and data(contains basic details like name, phone, etc.) from angular and save it on mongoDB. Also I dont have any idea how to send my image from angular to nestjs and how to save it(and where to save it on MongoDB or Nestjs server and how)
Any help would be greatly appreciated!!!
Thanks in advance.
To access to your files with NestJs you need to define your static assets directory name in the main.ts using the .useStaticAssets method on your app
app.useStaticAssets(join(__dirname, '..', 'public'), {
index: false,
prefix: '/public',
});
I found solution to this. So what we have to do is basically append all our data in formData and send it in the request from angular.
let formData = new FormData();
formData.append('image',this.image);
Now this image attribute is taken from the function triggered by the onchange on the input tag that takes image as input.
onChange(event){
this.image = event.target.files[0];
}
Now we send it to backend from our service.
sendReq(formData){
this.http.post('localhost:3000/your_route',formData);
}
Now while accessing it from the Nestjs server we use FileInterceptor.
import { Controller, Get, Post, Res, UploadedFile, UseInterceptors, Body } from '#nestjs/common';
import { FileInterceptor } from '#nestjs/platform-express';
import { editFileName, imageFileFilter } from './funcs';
import { diskStorage } from 'multer';
#Post('your_route')
#UseInterceptors(FileInterceptor('image',{
storage: diskStorage({
destination: './uploads',
filename: editFileName,
}),
fileFilter: imageFileFilter,
}))
async func(#UploadedFile() file, #Body() body){
try{
body.image = file.filename;
body.view = false;
let res = await this.yourService.yourFunc(body);
return {
'success': true,
'data': res
}
}
catch(err){
console.log(err);
return {
'success': false,
'data': err
}
}
}
const imageFileFilter = (req, file, callback) => {
if (!file.originalname.match(/\.(jpg|jpeg|png|gif)$/)) {
return callback(new Error('Only image files are allowed!'), false);
}
callback(null, true);
};
const editFileName = (req, file, callback) => {
const name = file.originalname.split('.')[0];
const fileExtName = '.'+file.originalname.split('.')[1];
const randomName = Array(4)
.fill(null)
.map(() => Math.round(Math.random() * 16).toString(16))
.join('');
callback(null, `${name}-${randomName}${fileExtName}`);
};
So this way we get a uploads folder in our root directory and image uploaded to our server gets saved here. To save the image the name I have user here is the name of the file being uploaded + '-' + a sequence of random char and int of length 4(Here you can have logic of your own).
I can either have my axios post send some data ( example below ) or FormData. How would I set the call up so that I can send it all at once. The problem is that if I send both simultaneously, it doesn't send anything at all. My current call is :
async handleSubmit(e) {
e.preventDefault();
const { name, email, message } = this.state;
const formData = new FormData();
formData.append('file',this.state.file)
const config = {
headers: {
'content-type': 'multipart/form-data'
}
}
const form = await axios.post("/api/formPDF", {
name, ******
email, *****
message ****
}).post("/api/formPDF", formData, config);
}
The section I have indicated with * is where I believe my problem to be. The way I have it send now, I will have access to name/email/message on req.body. If I remove the object of the three, and replace it with formData it will correctly email the file but everything is clearly undefined. If I edit it as so :
const form = await axios.post("/api/formPDF", {
name,
email,
message,
formData
It makes it so both my req.body and the way I parse my file is an empty object. My relevant server code is :
app.post("/api/formPDF", (req, res) => {
var fileLoc, fileExt, fileName, fileEmail, fileMessage;
var form = new formidable.IncomingForm();
form.parse(req, function(err, fields, files) {
console.log("Files: ", files);
fileLoc = files.file.path;
fileExt = files.file.name.split('.').pop();
});
nodemailer.createTestAccount((err, account) => {
const htmlEmail = `
<h3>Contact Details</h3>
<ul>
<li>Name: ${req.body.name}</li>
<li>Email: ${req.body.email}</li>
</ul>
<h3>Message</h3>
<p>${req.body.message}</p>
`
let transporter = nodemailer.createTransport({
name: *removed*,
host: *removed*,
port: 465,
secure: true,
auth: {
user: *removed*,
pass: *removed*
}
})
let mailOptions = {
from: *removed*,
to: *removed*,
replyTo: req.body.email,
subject: "New Message",
text: req.body.message,
html: htmlEmail,
attachments: [
{
filename: `${req.body.name}Resume.${fileExt}`,
path: fileLoc
}
]
};
fileLoc = "";
transporter.sendMail(mailOptions, (err, info) => {
if (err) {
return console.log(err)
}
})
})
});
Am I missing something that is causing the objects to be empty? I know its not possible to read the formData client side, but I should be able to see it on my server. Thanks in advance.
I have code that send email with nodemailer in nodejs but I want to attach file to an email but I can't find way to do that I search on net but I could't find something useful.Is there any way that I can attach files to with that or any resource that can help me to attach file with nodemailer?
var nodemailer = require('nodemailer');
var events = require('events');
var check =1;
var events = new events.EventEmitter();
var smtpTransport = nodemailer.createTransport("SMTP",{
service: "gmail",
auth: {
user: "example#gmail.com",
pass: "pass"
}
});
function inputmail(){
///////Email
const from = 'example<example#gmail.com>';
const to = 'example#yahoo.com';
const subject = 'example';
const text = 'example email';
const html = '<b>example email</b>';
var mailOption = {
from: from,
to: to,
subject: subject,
text: text,
html: html
}
return mailOption;
}
function send(){
smtpTransport.sendMail(inputmail(),function(err,success){
if(err){
events.emit('error', err);
}
if(success){
events.emit('success', success);
}
});
}
///////////////////////////////////
send();
events.on("error", function(err){
console.log("Mail not send");
if(check<10)
send();
check++;
});
events.on("success", function(success){
console.log("Mail send");
});
Include in the var mailOption the key attachments, as follow:
var mailOptions = {
...
attachments: [
{ // utf-8 string as an attachment
filename: 'text1.txt',
content: 'hello world!'
},
{ // binary buffer as an attachment
filename: 'text2.txt',
content: new Buffer('hello world!','utf-8')
},
{ // file on disk as an attachment
filename: 'text3.txt',
path: '/path/to/file.txt' // stream this file
},
{ // filename and content type is derived from path
path: '/path/to/file.txt'
},
{ // stream as an attachment
filename: 'text4.txt',
content: fs.createReadStream('file.txt')
},
{ // define custom content type for the attachment
filename: 'text.bin',
content: 'hello world!',
contentType: 'text/plain'
},
{ // use URL as an attachment
filename: 'license.txt',
path: 'https://raw.github.com/andris9/Nodemailer/master/LICENSE'
},
{ // encoded string as an attachment
filename: 'text1.txt',
content: 'aGVsbG8gd29ybGQh',
encoding: 'base64'
},
{ // data uri as an attachment
path: 'data:text/plain;base64,aGVsbG8gd29ybGQ='
}
]}
Choose the option that adjust to your needs.
Link:Nodemailer Repository GitHub
Good Luck!!
Your code is almost right, just need to add, "attachments" property for attaching the files in your mail,
YOUR mailOption:
var mailOption = {
from: from,
to: to,
subject: subject,
text: text,
html: html
}
Just add attachments like
var mailOption = {
from: from,
to: to,
subject: subject,
text: text,
html: html,
attachments: [{
filename: change with filename,
path: change with file path
}]
}
attachments also provide some other way to attach file for more information check nodemailer community's documentation HERE
If you are passing options object in mail composer constructor and attachment is on http server then it should look like:
const options = {
attachments = [
{ // use URL as an attachment
filename: 'xxx.jpg',
path: 'http:something.com/xxx.jpg'
}
]
}
var express = require('express');
var router = express(),
multer = require('multer'),
upload = multer(),
fs = require('fs'),
path = require('path');
nodemailer = require('nodemailer'),
directory = path.dirname("");
var parent = path.resolve(directory, '..');
// your path to store the files
var uploaddir = parent + (path.sep) + 'emailprj' + (path.sep) + 'public' + (path.sep) + 'images' + (path.sep);
/* GET home page. */
router.get('/', function(req, res) {
res.render('index.ejs', {
title: 'Express'
});
});
router.post('/sendemail', upload.any(), function(req, res) {
var file = req.files;
console.log(file[0].originalname)
fs.writeFile(uploaddir + file[0].originalname, file[0].buffer, function(err) {
//console.log("filewrited")
//console.log(err)
})
var filepath = path.join(uploaddir, file[0].originalname);
console.log(filepath)
//return false;
nodemailer.mail({
from: "yourgmail.com",
to: req.body.emailId, // list of receivers
subject: req.body.subject + " ✔", // Subject line
html: "<b>" + req.body.description + "</b>", // html body
attachments: [{
filename: file[0].originalname,
streamSource: fs.createReadStream(filepath)
}]
});
res.send("Email has been sent successfully");
})
module.exports = router;
attachments: [
{
filename: "inovices_1.pdf", // the file name
path: "https://*************************/invoice/10_9_RMKUns.pdf",// link your file
contentType: "application/pdf", //type of file
},
{
filename: "inovices_2.pdf",
path: "https://**************************/invoice/10_9_RMKUns.pdf",
contentType: "application/pdf",
},
];
var nodemailer = require("nodemailer");
var all_transporter = nodemailer.createTransport({
host: process.env.MAIL_SERVICE,
port: 587,
auth: {
user: process.env.MAIL_USER,
pass: process.env.MAIL_PASS,
},
maxConnections: 3,
pool: true,
});
exports.send_email = function (email, subject, html, extra_cc = [], attachments = []) {
return new Promise(async (resolve, reject) => {
var mailOptions = {
from: process.env.MAIL_FROM_ADDRESS,
to: email,
subject: subject,
html: html,
cc: [],
};
mailOptions["cc"] = mailOptions["cc"].concat(extra_cc);
if (attachments.length > 0) mailOptions["attachments"] = attachments;
all_transporter.sendMail(mailOptions, function (error, info) {
// console.log(error);
// console.log(info);
if (error) {
resolve({ failed: true, err: error });
} else {
resolve({ failed: false, data: info.response });
}
});
});
};
The alternative solution is to host your images online using a CDN and link to the online image source in your HTML, eg. <img src="list_image_url_here">.
(I had problems with nodemailer's image embedding using nodemailer version 2.6.0, which is why I figured out this workaround.)
An added benefit of this solution is that you're sending no attachments to nodemailer, so the sending process is more streamlined.
var mailer = require('nodemailer');
mailer.SMTP = {
host: 'host.com',
port:587,
use_authentication: true,
user: 'you#example.com',
pass: 'xxxxxx'
};
Then read a file and send an email :
fs.readFile("./attachment.txt", function (err, data) {
mailer.send_mail({
sender: 'sender#sender.com',
to: 'dest#dest.com',
subject: 'Attachment!',
body: 'mail content...',
attachments: [{'filename': 'attachment.txt', 'content': data}]
}), function(err, success) {
if (err) {
// Handle error
}
}
});
Just look at here. Nodemailer > Message configuration > Attachments
The code snippet is below (pdfkit gets the stream):
// in async func
pdf.end();
const stream = pdf;
const attachments = [{ filename: 'fromFile.pdf', path: './output.pdf',
contentType: 'application/pdf' }, { filename: 'fromStream.pdf', content: stream, contentType: 'application/pdf' }];
await sendMail('"Sender" <sender#test.com>', 'reciver#test.com', 'Test Send Files', '<h1>Hello</h1>', attachments);
Stream uses content not streamSource This bothered me before, share with everyone :)
Reference = https://nodemailer.com/message/attachments/
var mailOption = {
from: from,
to: to,
subject: subject,
text: text,
html: html,
attachments: [
{
filename: filename,
path: filePath
},
]
}