gmail-to-exchange365
Version:
Complete Gmail to Exchange 365 migration tool with UI - Migrate emails, attachments, and folders seamlessly
155 lines (133 loc) • 4.3 kB
text/typescript
import { Client } from "@microsoft/microsoft-graph-client";
import { EmailMessage } from "./types";
interface MSToken {
access_token: string;
refresh_token?: string;
expires_in?: number;
}
export function getGraphClient(msToken: MSToken) {
return Client.init({
authProvider: (done) => {
done(null, msToken.access_token);
}
});
}
export async function uploadEmail(
client: Client,
email: EmailMessage,
folderId: string = "Inbox"
): Promise<void> {
try {
const eml = buildMime(email);
// Use the MIME message endpoint
await client
.api(`/me/mailFolders/${folderId}/messages`)
.header("Content-Type", "message/rfc822")
.post(eml);
} catch (error: any) {
// If MIME upload fails, try creating message via Graph API
if (error.statusCode === 400 || error.statusCode === 415) {
await uploadEmailAsGraphMessage(client, email, folderId);
} else {
throw error;
}
}
}
async function uploadEmailAsGraphMessage(
client: Client,
email: EmailMessage,
folderId: string
): Promise<void> {
const message: any = {
subject: email.subject,
from: {
emailAddress: {
address: email.from
}
},
toRecipients: email.to.map(addr => ({
emailAddress: { address: addr }
})),
body: {
contentType: email.htmlBody ? "html" : "text",
content: email.htmlBody || email.textBody || ""
},
receivedDateTime: email.date,
sentDateTime: email.date
};
if (email.cc && email.cc.length > 0) {
message.ccRecipients = email.cc.map(addr => ({
emailAddress: { address: addr }
}));
}
if (email.bcc && email.bcc.length > 0) {
message.bccRecipients = email.bcc.map(addr => ({
emailAddress: { address: addr }
}));
}
const createdMessage = await client
.api(`/me/mailFolders/${folderId}/messages`)
.post(message);
// Upload attachments separately
if (email.attachments && email.attachments.length > 0) {
for (const attachment of email.attachments) {
await uploadAttachment(client, createdMessage.id, attachment);
}
}
}
async function uploadAttachment(
client: Client,
messageId: string,
attachment: { filename: string; mimeType: string; data: Buffer }
): Promise<void> {
try {
await client
.api(`/me/messages/${messageId}/attachments`)
.post({
"@odata.type": "#microsoft.graph.fileAttachment",
name: attachment.filename,
contentType: attachment.mimeType,
contentBytes: attachment.data.toString("base64")
});
} catch (error: any) {
console.error(`Error uploading attachment ${attachment.filename}:`, error.message);
}
}
export function buildMime(email: EmailMessage): Buffer {
let mime = "";
// Headers
mime += `From: ${email.from}\r\n`;
mime += `To: ${email.to.join(", ")}\r\n`;
if (email.cc && email.cc.length > 0) {
mime += `Cc: ${email.cc.join(", ")}\r\n`;
}
if (email.bcc && email.bcc.length > 0) {
mime += `Bcc: ${email.bcc.join(", ")}\r\n`;
}
mime += `Subject: ${email.subject}\r\n`;
mime += `Date: ${email.date}\r\n`;
mime += `MIME-Version: 1.0\r\n`;
const boundary = "----=_Part_" + Date.now();
mime += `Content-Type: multipart/mixed; boundary="${boundary}"\r\n\r\n`;
// Body
mime += `--${boundary}\r\n`;
if (email.htmlBody) {
mime += `Content-Type: text/html; charset=utf-8\r\n`;
mime += `Content-Transfer-Encoding: 7bit\r\n\r\n`;
mime += `${email.htmlBody}\r\n\r\n`;
} else if (email.textBody) {
mime += `Content-Type: text/plain; charset=utf-8\r\n`;
mime += `Content-Transfer-Encoding: 7bit\r\n\r\n`;
mime += `${email.textBody}\r\n\r\n`;
}
// Attachments
for (const attachment of email.attachments || []) {
mime += `--${boundary}\r\n`;
mime += `Content-Type: ${attachment.mimeType}\r\n`;
mime += `Content-Disposition: attachment; filename="${attachment.filename}"\r\n`;
mime += `Content-Transfer-Encoding: base64\r\n\r\n`;
mime += attachment.data.toString("base64") + "\r\n\r\n";
}
mime += `--${boundary}--\r\n`;
return Buffer.from(mime, "utf-8");
}