问题
I am trying to send reply to a thread (mail) with attachment using gmail api. But it is being sent as a new mail in senders account, but in receiver account it is seen in same thread. This mail should be in same thread for both sender and receiver sides as well.
I am using node js and request-promise module for achieving this. Here is my piece of code. Please have a look and let me know where I am wrong.
let from = req.body.email;
let mailString = '';
for (let i in req.files) {
mailString += '--boundary_mail1\n'
mailString += `Content-Type: ${req.files[i].mimetype}\n`
mailString += `Content-Disposition: attachment; filename="${req.files[i].filename}"\n`
mailString += "Content-Transfer-Encoding: base64\n\n"
mailString += `${fs.readFileSync(req.files[i].path).toString('base64')}` + '\n'
}
let raw = [
'MIME-Version: 1.0\n',
"to: ", reply.mailContent.to, "\n",
"from: ", from, "\n",
"threadId: ", reply.mailContent.id, "\n",
"References: ", reply.mailContent.references, "\n",
"In-Reply-To: ", reply.mailContent.messageId, "\n",
"subject: ", reply.mailContent.subject, "\n",
"Content-Type: multipart/mixed; boundary=boundary_mail1\n\n",
"--boundary_mail1\n",
"Content-Type: multipart/alternative; boundary=boundary_mail2\n\n",
"--boundary_mail2\n",
"Content-Type: text/html; charset=UTF-8\n",
"Content-Transfer-Encoding: quoted-printable\n\n",
req.body.message = req.body.message ? req.body.message : '', "\n\n",
"--boundary_mail2--\n",
mailString,
'--boundary_mail1--',
].join('');
const id = 'me';
let options = {
url: "https://www.googleapis.com/upload/gmail/v1/users/" + id + "/messages/send?uploadType=multipart",
method: 'POST',
headers: {
'Authorization': `Bearer ${user.options.access_token}`,
'Content-Type': 'message/rfc822'
},
body: raw
};
await request(options).then(async b => {
return res.apiOk();
}).catch(err => {
return res.apiError(err);
})
}).catch(err => {
return res.apiError("Something went wrong!");
})
回答1:
It's a bit complicated to explain how should it be.
I had the same problem, after a while, I ended up this js-function to create a Gmail-Message-Body. It works fine now.
Lets dig inside:
Gmail Body Maker
function createJson(threadId) {
const thread = threadId ? `"threadId": "${threadId}"\r\n` : ''
return [
'Content-Type: application/json; charset="UTF-8"\r\n\r\n',
`{\r\n${thread}}`
].join('')
}
function createHeaders(headers) {
if ( !headers || headers.length === 0 )
return ''
const result = []
for (const h in headers) {
if (headers.hasOwnProperty(h)) {
result.push(`${h}: ${headers[h]}\r\n`)
}
}
return result.join('')
}
function createPlain(text) {
return [
'Content-Type: text/plain; charset="UTF-8"\r\n',
'MIME-Version: 1.0\r\n',
'Content-Transfer-Encoding: 7bit\r\n\r\n',
text
].join('')
}
function createHtml(html) {
return [
'Content-Type: text/html; charset="UTF-8"\r\n',
'MIME-Version: 1.0\r\n',
'Content-Transfer-Encoding: 7bit\r\n\r\n',
html
].join('')
}
function createAlternative(text, html) {
return [
'Content-Type: multipart/alternative; boundary="foo"\r\n\r\n',
'--foo\r\n',
createPlain(text),
'\r\n\r\n',
'--foo\r\n',
createHtml(html),
'\r\n\r\n',
'--foo--',
].join('')
}
function createBody(text, html) {
if (text && html)
return createAlternative(text, html)
if (text)
return createPlain(text)
if (html)
return createHtml(html)
return ''
}
function createAttachments(attachments) {
if ( !attachments || attachments.length === 0 )
return ''
const result = []
for (let i = 0; i < attachments.length; i++) {
const att = attachments[i]
const attName = att.filename ? `; filename="${att.filename}"` : ''
// Content-Type: image/jpeg
// MIME-Version: 1.0
// Content-ID: <20180619202303.24365.655.img@domain>
// Content-Transfer-Encoding: base64
// Content-Disposition: inline
result.push('--foo_bar\r\n')
result.push(`Content-Type: ${att.type}\r\n`)
result.push('MIME-Version: 1.0\r\n')
if (att.contentId) result.push(`Content-ID: <${att.contentId}>\r\n`)
result.push('Content-Transfer-Encoding: base64\r\n')
result.push(`Content-Disposition: attachment${attName}\r\n\r\n`)
result.push(`${att.content}\r\n\r\n`)
}
return result.join('')
}
function gmailBodyMaker(params) {
const json = createJson(params.threadId)
const headers = createHeaders(params.headers)
const body = createBody(params.text, params.html)
const attachments = createAttachments(params.attachments)
return [
'--foo_bar_baz\r\n',
`${json}\r\n\r\n`,
'--foo_bar_baz\r\n',
'Content-Type: message/rfc822\r\n\r\n',
'Content-Type: multipart/mixed; boundary="foo_bar"\r\n',
`${headers}\r\n`,
'--foo_bar\r\n',
`${body}\r\n\r\n`,
attachments,
'--foo_bar--\r\n\r\n',
'--foo_bar_baz--',
].join('')
}
Create Email Object
const createEmailObject = async function (params) {
const attachments = []
for (const att of params.attachments) {
const imageData = await request.get({
url: att.link,
encoding: 'binary'
})
const base64 = Buffer.from(imageData, 'binary').toString('base64')
const size = base64.length // in bytes
if ( size > 5242880)
throw Error.BadRequest('File-size could not be greater than 4-MB!')
attachments.push({
'filename': att.filename,
'contentId': att.contentId || null, // <img src="cid:some-image-cid" alt="img" />
'content': base64,
'encoding': 'base64',
'type': att.type
})
}
const toRecipients = params.header.to ? params.header.to.map(record => (record.address)) : []
const ccRecipients = params.header.cc ? params.header.cc.map(record => (record.address)) : []
const bccRecipients = params.header.bcc ? params.header.bcc.map(record => (record.address)) : []
return {
headers: {
From: params.header.from,
To: toRecipients.join(','),
Cc: ccRecipients.join(','),
Bcc: bccRecipients.join(','),
'In-Reply-To': params.header['In-Reply-To'],
Subject: params.header.subject
},
threadId: params.threadId,
attachments: attachments,
text: params.body.text,
html: params.body.html
}
}
Email Senders
// Highly recommend to use this method
Google.prototype.sendMultipartMessage = async function (email) {
// File Size Limit: Up To 35 MB
const authHeader = await this.oAuth2Client.getRequestHeaders()
const arr = authHeader.Authorization.split('Bearer ')
const token = arr[1]
const responseString = await request.post({
url: 'https://www.googleapis.com/upload/gmail/v1/users/me/messages/send?uploadType=multipart',
headers: {
'Authorization': `OAuth ${token}`,
'Content-Type': 'multipart/related; boundary="foo_bar_baz"'
},
body: gmailBodyMaker(email)
})
return JSON.parse(responseString)
}
// Alternative Method
Google.prototype.sendMessage = async function (email) {
const message = [
'Content-Type: text/plain; charset="UTF-8"',
'MIME-Version: 1.0',
'Content-Transfer-Encoding: 7bit',
`To: ${email.to}`,
`Subject: ${email.subject}`,
'', // Extra new line required
`${email.htmlBody}`
].join('\n')
const raw = Buffer.from(message).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
const response = await this.gmail.users.messages.send({
auth: this.oAuth2Client,
userId: this.gmailAddress,
resource: {
threadId: email.threadId,
raw: raw
}
})
return response
}
// Alternative Method
Google.prototype.sendMessageWithAttachment = async function (email) {
// File Size Limit: 5 MB
const message = new MailComposer({
to: email.to,
subject: email.subject,
text: email.textBody,
html: email.htmlBody,
textEncoding: 'base64',
attachments: email.attachments
})
const builtMessage = await message.compile().build()
const raw = Buffer.from(builtMessage).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
const response = await this.gmail.users.messages.send({
auth: this.oAuth2Client,
userId: this.gmailAddress,
uploadType: 'multipart',
resource: {
threadId: email.threadId,
raw: raw
}
})
return response
}
Main Script
const params = {
'header': {
'subject': 'your-subject',
'from': 'name <email@domain.com> ',
'to': [{'address': 'email_2@domain.com', 'name': 'name'}],
'cc': [{'address': 'email_3@domain.com', 'name': 'name'}],
'bcc': [{'address': 'email_4@domain.com', 'name': 'name'}],
'In-Reply-To': `<any_valid_internet_message_id_goes_here>` || null
},
'threadId': 'any_valid_thread_id_goes_here' || null,
'attachments': [
{
'filename': 'filename.jpg',
'link': 'https://some-where.com/my_image.jpg',
'type': 'image/jpeg',
'isInline': true,
'contentId': 'any_valid_content_id'
}
],
'body': {
'text': 'text',
'html': 'html'
}
}
// Solution 1
const body = await createEmailObject(params)
const result = await google.sendMultipartMessage(body)
const sentMessageId = result.id
来源:https://stackoverflow.com/questions/54555904/reply-to-a-thread-mail-with-attachment-being-sent-as-a-new-mail-in-sender-acco