mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-09-09 14:16:02 +08:00
[local-sync, iso-core] feat(send): add finishing touches to gmail multi-send
Delete the sent messages that gmail automatically creates and save our generic form of the draft as a sent message.
This commit is contained in:
parent
1a24840062
commit
e785d73bdc
8 changed files with 87 additions and 42 deletions
|
@ -1,4 +1,5 @@
|
||||||
const _ = require('underscore');
|
const _ = require('underscore');
|
||||||
|
const PromiseUtils = require('./promise-utils')
|
||||||
|
|
||||||
const {
|
const {
|
||||||
IMAPConnectionNotReadyError,
|
IMAPConnectionNotReadyError,
|
||||||
|
@ -173,6 +174,24 @@ class IMAPBox {
|
||||||
return this._conn._imap.delLabelsAsync(range, labels)
|
return this._conn._imap.delLabelsAsync(range, labels)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
append(rawMime, options) {
|
||||||
|
if (!this._conn._imap) {
|
||||||
|
throw new IMAPConnectionNotReadyError(`IMAPBox::append`)
|
||||||
|
}
|
||||||
|
return PromiseUtils.promisify(this._conn._imap.append).call(
|
||||||
|
this._conn._imap, rawMime, options
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
search(criteria) {
|
||||||
|
if (!this._conn._imap) {
|
||||||
|
throw new IMAPConnectionNotReadyError(`IMAPBox::search`)
|
||||||
|
}
|
||||||
|
return PromiseUtils.promisify(this._conn._imap.search).call(
|
||||||
|
this._conn._imap, criteria
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
closeBox({expunge = true} = {}) {
|
closeBox({expunge = true} = {}) {
|
||||||
if (!this._conn._imap) {
|
if (!this._conn._imap) {
|
||||||
throw new IMAPConnectionNotReadyError(`IMAPBox::closeBox`)
|
throw new IMAPConnectionNotReadyError(`IMAPBox::closeBox`)
|
||||||
|
|
|
@ -168,10 +168,10 @@ module.exports = (server) => {
|
||||||
// gmail creates sent messages for each one, go through and delete them
|
// gmail creates sent messages for each one, go through and delete them
|
||||||
if (account.provider === 'gmail') {
|
if (account.provider === 'gmail') {
|
||||||
try {
|
try {
|
||||||
// TODO: use type: "PermananentDeleteMessage" once it's fully implemented
|
|
||||||
await db.SyncbackRequest.create({
|
await db.SyncbackRequest.create({
|
||||||
type: "DeleteMessage",
|
accountId: account.id,
|
||||||
props: { messageId: draft.id },
|
type: "DeleteSentMessage",
|
||||||
|
props: { messageId: `${draft.id}@nylas.com` },
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Even if this fails, we need to finish the multi-send session,
|
// Even if this fails, we need to finish the multi-send session,
|
||||||
|
@ -185,7 +185,7 @@ module.exports = (server) => {
|
||||||
await db.SyncbackRequest.create({
|
await db.SyncbackRequest.create({
|
||||||
accountId: account.id,
|
accountId: account.id,
|
||||||
type: "SaveSentMessage",
|
type: "SaveSentMessage",
|
||||||
props: {rawMime},
|
props: {rawMime, messageId: `${draft.id}@nylas.com`},
|
||||||
});
|
});
|
||||||
|
|
||||||
await (draft.isSent = true);
|
await (draft.isSent = true);
|
||||||
|
|
|
@ -57,6 +57,7 @@ class SendmailClient {
|
||||||
}
|
}
|
||||||
msgData.subject = draft.subject;
|
msgData.subject = draft.subject;
|
||||||
msgData.html = draft.body;
|
msgData.html = draft.body;
|
||||||
|
msgData.messageId = `${draft.id}@nylas.com`;
|
||||||
|
|
||||||
// TODO: attachments
|
// TODO: attachments
|
||||||
|
|
||||||
|
@ -69,18 +70,17 @@ class SendmailClient {
|
||||||
msgData.headers = draft.headers;
|
msgData.headers = draft.headers;
|
||||||
msgData.headers['User-Agent'] = `NylasMailer-K2`
|
msgData.headers['User-Agent'] = `NylasMailer-K2`
|
||||||
|
|
||||||
// TODO: do we want to set messageId or date?
|
|
||||||
|
|
||||||
return msgData;
|
return msgData;
|
||||||
}
|
}
|
||||||
|
|
||||||
async buildMime(draft) {
|
async buildMime(draft) {
|
||||||
const builder = mailcomposer(this._draftToMsgData(draft))
|
const builder = mailcomposer(this._draftToMsgData(draft))
|
||||||
return new Promise((resolve, reject) => {
|
const mimeNode = await (new Promise((resolve, reject) => {
|
||||||
builder.build((error, result) => {
|
builder.build((error, result) => {
|
||||||
error ? reject(error) : resolve(result)
|
error ? reject(error) : resolve(result)
|
||||||
})
|
})
|
||||||
})
|
}));
|
||||||
|
return mimeNode.toString('ascii')
|
||||||
}
|
}
|
||||||
|
|
||||||
async send(draft) {
|
async send(draft) {
|
||||||
|
|
|
@ -56,6 +56,8 @@ class SyncbackTaskFactory {
|
||||||
Task = require('./syncback_tasks/delete-message.imap'); break;
|
Task = require('./syncback_tasks/delete-message.imap'); break;
|
||||||
case "SaveSentMessage":
|
case "SaveSentMessage":
|
||||||
Task = require('./syncback_tasks/save-sent-message.imap'); break;
|
Task = require('./syncback_tasks/save-sent-message.imap'); break;
|
||||||
|
case "DeleteSentMessage":
|
||||||
|
Task = require('./syncback_tasks/delete-sent-message.gmail'); break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Task type not defined in syncback-task-factory: ${syncbackRequest.type}`)
|
throw new Error(`Task type not defined in syncback-task-factory: ${syncbackRequest.type}`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,10 @@ class DeleteMessageIMAP extends SyncbackTask {
|
||||||
return `DeleteMessage`;
|
return `DeleteMessage`;
|
||||||
}
|
}
|
||||||
|
|
||||||
run(db, imap) {
|
async run(db, imap) {
|
||||||
const messageId = this.syncbackRequestObject().props.messageId
|
const messageId = this.syncbackRequestObject().props.messageId
|
||||||
|
const {box, message} = await TaskHelpers.openMessageBox({messageId, db, imap})
|
||||||
return TaskHelpers.openMessageBox({messageId, db, imap})
|
return box.addFlags(message.folderImapUID, ['DELETED'])
|
||||||
.then(({box, message}) => {
|
|
||||||
return box.addFlags(message.folderImapUID, 'DELETED')
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module.exports = DeleteMessageIMAP;
|
module.exports = DeleteMessageIMAP;
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
const SyncbackTask = require('./syncback-task')
|
||||||
|
|
||||||
|
class DeleteSentMessageGMAIL extends SyncbackTask {
|
||||||
|
description() {
|
||||||
|
return `DeleteSentMessage`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(db, imap) {
|
||||||
|
const {messageId} = this.syncbackRequestObject().props
|
||||||
|
|
||||||
|
const trash = await db.Folder.find({where: {role: 'trash'}});
|
||||||
|
if (!trash) { throw new Error(`Could not find folder with role 'trash'.`) }
|
||||||
|
|
||||||
|
const allMail = await db.Folder.find({where: {role: 'all'}});
|
||||||
|
if (!allMail) { throw new Error(`Could not find folder with role 'all'.`) }
|
||||||
|
|
||||||
|
// Move the message from all mail to trash and then delete it from there
|
||||||
|
const steps = [
|
||||||
|
{folder: allMail, deleteFn: (box, uid) => box.moveFromBox(uid, trash.name)},
|
||||||
|
{folder: trash, deleteFn: (box, uid) => box.addFlags(uid, 'DELETED')},
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const {folder, deleteFn} of steps) {
|
||||||
|
const box = await imap.openBox(folder.name);
|
||||||
|
const uids = await box.search([['HEADER', 'Message-ID', messageId]])
|
||||||
|
for (const uid of uids) {
|
||||||
|
await deleteFn(box, uid);
|
||||||
|
}
|
||||||
|
box.closeBox();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module.exports = DeleteSentMessageGMAIL;
|
|
@ -1,24 +0,0 @@
|
||||||
const SyncbackTask = require('./syncback-task')
|
|
||||||
|
|
||||||
class PermanentlyDeleteMessageIMAP extends SyncbackTask {
|
|
||||||
description() {
|
|
||||||
return `PermanentlyDeleteMessage`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async run(db, imap) {
|
|
||||||
const messageId = this.syncbackRequestObject().props.messageId
|
|
||||||
const message = await db.Message.findById(messageId);
|
|
||||||
const folder = await db.Folder.findById(message.folderId);
|
|
||||||
const box = await imap.openBox(folder.name);
|
|
||||||
const result = await box.addFlags(message.folderImapUID, 'DELETED');
|
|
||||||
return result;
|
|
||||||
|
|
||||||
// TODO: We need to also delete the message from the trash
|
|
||||||
// if (folder.role === 'trash') { return result; }
|
|
||||||
//
|
|
||||||
// const trash = await db.Folder.find({where: {role: 'trash'}});
|
|
||||||
// const trashBox = await imap.openBox(trash.name);
|
|
||||||
// return await trashBox.addFlags(message.folderImapUID, 'DELETED');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = PermanentlyDeleteMessageIMAP;
|
|
|
@ -6,10 +6,28 @@ class SaveSentMessageIMAP extends SyncbackTask {
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(db, imap) {
|
async run(db, imap) {
|
||||||
// TODO: gmail doesn't have a sent folder
|
const {rawMime, messageId} = this.syncbackRequestObject().props;
|
||||||
const folder = await db.Folder.find({where: {role: 'sent'}});
|
|
||||||
const box = await imap.openBox(folder.name);
|
// Non-gmail
|
||||||
return box.append(this.syncbackRequestObject().props.rawMime);
|
const sentFolder = await db.Folder.find({where: {role: 'sent'}});
|
||||||
|
if (sentFolder) {
|
||||||
|
const box = await imap.openBox(sentFolder.name);
|
||||||
|
return box.append(rawMime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gmail, we need to add the message to all mail and add the sent label
|
||||||
|
const sentLabel = await db.Label.find({where: {role: 'sent'}});
|
||||||
|
const allMail = await db.Folder.find({where: {role: 'all'}});
|
||||||
|
if (sentLabel && allMail) {
|
||||||
|
let box = await imap.openBox(allMail.name);
|
||||||
|
await box.append(rawMime, {flags: 'SEEN'})
|
||||||
|
const uids = await box.search([['HEADER', 'Message-ID', messageId]])
|
||||||
|
// There should only be one uid in the array
|
||||||
|
return box.setLabels(uids[0], '\\Sent');
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Could not save message to sent folder.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = SaveSentMessageIMAP;
|
module.exports = SaveSentMessageIMAP;
|
||||||
|
|
Loading…
Add table
Reference in a new issue