mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
improvement(reply-gmail): added reply to gmail (#1809)
* added reply to thread/message * cleanup, extract header helper for threaded replies * more helpers
This commit is contained in:
@@ -5,7 +5,12 @@ import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
|
||||
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
|
||||
import { generateRequestId } from '@/lib/utils'
|
||||
import { base64UrlEncode, buildMimeMessage } from '@/tools/gmail/utils'
|
||||
import {
|
||||
base64UrlEncode,
|
||||
buildMimeMessage,
|
||||
buildSimpleEmailMessage,
|
||||
fetchThreadingHeaders,
|
||||
} from '@/tools/gmail/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@@ -16,8 +21,10 @@ const GMAIL_API_BASE = 'https://gmail.googleapis.com/gmail/v1/users/me'
|
||||
const GmailDraftSchema = z.object({
|
||||
accessToken: z.string().min(1, 'Access token is required'),
|
||||
to: z.string().min(1, 'Recipient email is required'),
|
||||
subject: z.string().min(1, 'Subject is required'),
|
||||
subject: z.string().optional().nullable(),
|
||||
body: z.string().min(1, 'Email body is required'),
|
||||
threadId: z.string().optional().nullable(),
|
||||
replyToMessageId: z.string().optional().nullable(),
|
||||
cc: z.string().optional().nullable(),
|
||||
bcc: z.string().optional().nullable(),
|
||||
attachments: z.array(z.any()).optional().nullable(),
|
||||
@@ -49,11 +56,19 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
logger.info(`[${requestId}] Creating Gmail draft`, {
|
||||
to: validatedData.to,
|
||||
subject: validatedData.subject,
|
||||
subject: validatedData.subject || '',
|
||||
hasAttachments: !!(validatedData.attachments && validatedData.attachments.length > 0),
|
||||
attachmentCount: validatedData.attachments?.length || 0,
|
||||
})
|
||||
|
||||
const threadingHeaders = validatedData.replyToMessageId
|
||||
? await fetchThreadingHeaders(validatedData.replyToMessageId, validatedData.accessToken)
|
||||
: {}
|
||||
|
||||
const originalMessageId = threadingHeaders.messageId
|
||||
const originalReferences = threadingHeaders.references
|
||||
const originalSubject = threadingHeaders.subject
|
||||
|
||||
let rawMessage: string | undefined
|
||||
|
||||
if (validatedData.attachments && validatedData.attachments.length > 0) {
|
||||
@@ -106,8 +121,10 @@ export async function POST(request: NextRequest) {
|
||||
to: validatedData.to,
|
||||
cc: validatedData.cc ?? undefined,
|
||||
bcc: validatedData.bcc ?? undefined,
|
||||
subject: validatedData.subject,
|
||||
subject: validatedData.subject || originalSubject || '',
|
||||
body: validatedData.body,
|
||||
inReplyTo: originalMessageId,
|
||||
references: originalReferences,
|
||||
attachments: attachmentBuffers,
|
||||
})
|
||||
|
||||
@@ -117,22 +134,21 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
if (!rawMessage) {
|
||||
const emailHeaders = [
|
||||
'Content-Type: text/plain; charset="UTF-8"',
|
||||
'MIME-Version: 1.0',
|
||||
`To: ${validatedData.to}`,
|
||||
]
|
||||
rawMessage = buildSimpleEmailMessage({
|
||||
to: validatedData.to,
|
||||
cc: validatedData.cc,
|
||||
bcc: validatedData.bcc,
|
||||
subject: validatedData.subject || originalSubject,
|
||||
body: validatedData.body,
|
||||
inReplyTo: originalMessageId,
|
||||
references: originalReferences,
|
||||
})
|
||||
}
|
||||
|
||||
if (validatedData.cc) {
|
||||
emailHeaders.push(`Cc: ${validatedData.cc}`)
|
||||
}
|
||||
if (validatedData.bcc) {
|
||||
emailHeaders.push(`Bcc: ${validatedData.bcc}`)
|
||||
}
|
||||
const draftMessage: { raw: string; threadId?: string } = { raw: rawMessage }
|
||||
|
||||
emailHeaders.push(`Subject: ${validatedData.subject}`, '', validatedData.body)
|
||||
const email = emailHeaders.join('\n')
|
||||
rawMessage = Buffer.from(email).toString('base64url')
|
||||
if (validatedData.threadId) {
|
||||
draftMessage.threadId = validatedData.threadId
|
||||
}
|
||||
|
||||
const gmailResponse = await fetch(`${GMAIL_API_BASE}/drafts`, {
|
||||
@@ -142,7 +158,7 @@ export async function POST(request: NextRequest) {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
message: { raw: rawMessage },
|
||||
message: draftMessage,
|
||||
}),
|
||||
})
|
||||
|
||||
|
||||
@@ -5,7 +5,12 @@ import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
|
||||
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
|
||||
import { generateRequestId } from '@/lib/utils'
|
||||
import { base64UrlEncode, buildMimeMessage } from '@/tools/gmail/utils'
|
||||
import {
|
||||
base64UrlEncode,
|
||||
buildMimeMessage,
|
||||
buildSimpleEmailMessage,
|
||||
fetchThreadingHeaders,
|
||||
} from '@/tools/gmail/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@@ -16,8 +21,10 @@ const GMAIL_API_BASE = 'https://gmail.googleapis.com/gmail/v1/users/me'
|
||||
const GmailSendSchema = z.object({
|
||||
accessToken: z.string().min(1, 'Access token is required'),
|
||||
to: z.string().min(1, 'Recipient email is required'),
|
||||
subject: z.string().min(1, 'Subject is required'),
|
||||
subject: z.string().optional().nullable(),
|
||||
body: z.string().min(1, 'Email body is required'),
|
||||
threadId: z.string().optional().nullable(),
|
||||
replyToMessageId: z.string().optional().nullable(),
|
||||
cc: z.string().optional().nullable(),
|
||||
bcc: z.string().optional().nullable(),
|
||||
attachments: z.array(z.any()).optional().nullable(),
|
||||
@@ -49,11 +56,19 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
logger.info(`[${requestId}] Sending Gmail email`, {
|
||||
to: validatedData.to,
|
||||
subject: validatedData.subject,
|
||||
subject: validatedData.subject || '',
|
||||
hasAttachments: !!(validatedData.attachments && validatedData.attachments.length > 0),
|
||||
attachmentCount: validatedData.attachments?.length || 0,
|
||||
})
|
||||
|
||||
const threadingHeaders = validatedData.replyToMessageId
|
||||
? await fetchThreadingHeaders(validatedData.replyToMessageId, validatedData.accessToken)
|
||||
: {}
|
||||
|
||||
const originalMessageId = threadingHeaders.messageId
|
||||
const originalReferences = threadingHeaders.references
|
||||
const originalSubject = threadingHeaders.subject
|
||||
|
||||
let rawMessage: string | undefined
|
||||
|
||||
if (validatedData.attachments && validatedData.attachments.length > 0) {
|
||||
@@ -106,8 +121,10 @@ export async function POST(request: NextRequest) {
|
||||
to: validatedData.to,
|
||||
cc: validatedData.cc ?? undefined,
|
||||
bcc: validatedData.bcc ?? undefined,
|
||||
subject: validatedData.subject,
|
||||
subject: validatedData.subject || originalSubject || '',
|
||||
body: validatedData.body,
|
||||
inReplyTo: originalMessageId,
|
||||
references: originalReferences,
|
||||
attachments: attachmentBuffers,
|
||||
})
|
||||
|
||||
@@ -117,22 +134,21 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
if (!rawMessage) {
|
||||
const emailHeaders = [
|
||||
'Content-Type: text/plain; charset="UTF-8"',
|
||||
'MIME-Version: 1.0',
|
||||
`To: ${validatedData.to}`,
|
||||
]
|
||||
rawMessage = buildSimpleEmailMessage({
|
||||
to: validatedData.to,
|
||||
cc: validatedData.cc,
|
||||
bcc: validatedData.bcc,
|
||||
subject: validatedData.subject || originalSubject,
|
||||
body: validatedData.body,
|
||||
inReplyTo: originalMessageId,
|
||||
references: originalReferences,
|
||||
})
|
||||
}
|
||||
|
||||
if (validatedData.cc) {
|
||||
emailHeaders.push(`Cc: ${validatedData.cc}`)
|
||||
}
|
||||
if (validatedData.bcc) {
|
||||
emailHeaders.push(`Bcc: ${validatedData.bcc}`)
|
||||
}
|
||||
const requestBody: { raw: string; threadId?: string } = { raw: rawMessage }
|
||||
|
||||
emailHeaders.push(`Subject: ${validatedData.subject}`, '', validatedData.body)
|
||||
const email = emailHeaders.join('\n')
|
||||
rawMessage = Buffer.from(email).toString('base64url')
|
||||
if (validatedData.threadId) {
|
||||
requestBody.threadId = validatedData.threadId
|
||||
}
|
||||
|
||||
const gmailResponse = await fetch(`${GMAIL_API_BASE}/messages/send`, {
|
||||
@@ -141,7 +157,7 @@ export async function POST(request: NextRequest) {
|
||||
Authorization: `Bearer ${validatedData.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ raw: rawMessage }),
|
||||
body: JSON.stringify(requestBody),
|
||||
})
|
||||
|
||||
if (!gmailResponse.ok) {
|
||||
|
||||
@@ -65,7 +65,7 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
|
||||
layout: 'full',
|
||||
placeholder: 'Email subject',
|
||||
condition: { field: 'operation', value: ['send_gmail', 'draft_gmail'] },
|
||||
required: true,
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
id: 'body',
|
||||
@@ -101,6 +101,27 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
|
||||
mode: 'advanced',
|
||||
required: false,
|
||||
},
|
||||
// Advanced Settings - Threading
|
||||
{
|
||||
id: 'threadId',
|
||||
title: 'Thread ID',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Thread ID to reply to (for threading)',
|
||||
condition: { field: 'operation', value: ['send_gmail', 'draft_gmail'] },
|
||||
mode: 'advanced',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
id: 'replyToMessageId',
|
||||
title: 'Reply to Message ID',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Gmail message ID (not RFC Message-ID) - use the "id" field from results',
|
||||
condition: { field: 'operation', value: ['send_gmail', 'draft_gmail'] },
|
||||
mode: 'advanced',
|
||||
required: false,
|
||||
},
|
||||
// Advanced Settings - Additional Recipients
|
||||
{
|
||||
id: 'cc',
|
||||
@@ -241,13 +262,18 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
|
||||
to: { type: 'string', description: 'Recipient email address' },
|
||||
subject: { type: 'string', description: 'Email subject' },
|
||||
body: { type: 'string', description: 'Email content' },
|
||||
threadId: { type: 'string', description: 'Thread ID to reply to (for threading)' },
|
||||
replyToMessageId: {
|
||||
type: 'string',
|
||||
description: 'Gmail message ID to reply to (use "id" field from results, not "messageId")',
|
||||
},
|
||||
cc: { type: 'string', description: 'CC recipients (comma-separated)' },
|
||||
bcc: { type: 'string', description: 'BCC recipients (comma-separated)' },
|
||||
attachments: { type: 'json', description: 'Files to attach (UserFile array)' },
|
||||
// Read operation inputs
|
||||
folder: { type: 'string', description: 'Gmail folder' },
|
||||
manualFolder: { type: 'string', description: 'Manual folder name' },
|
||||
messageId: { type: 'string', description: 'Message identifier' },
|
||||
readMessageId: { type: 'string', description: 'Message identifier for reading specific email' },
|
||||
unreadOnly: { type: 'boolean', description: 'Unread messages only' },
|
||||
includeAttachments: { type: 'boolean', description: 'Include email attachments' },
|
||||
// Search operation inputs
|
||||
|
||||
@@ -368,7 +368,7 @@ async function processOutlookEmails(
|
||||
const simplifiedEmail: SimplifiedOutlookEmail = {
|
||||
id: email.id,
|
||||
conversationId: email.conversationId,
|
||||
subject: email.subject || '(No Subject)',
|
||||
subject: email.subject || '',
|
||||
from: email.from?.emailAddress?.address || '',
|
||||
to: email.toRecipients?.map((r) => r.emailAddress.address).join(', ') || '',
|
||||
cc: email.ccRecipients?.map((r) => r.emailAddress.address).join(', ') || '',
|
||||
|
||||
@@ -28,7 +28,7 @@ export const gmailDraftTool: ToolConfig<GmailSendParams, GmailToolResponse> = {
|
||||
},
|
||||
subject: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Email subject',
|
||||
},
|
||||
@@ -38,6 +38,19 @@ export const gmailDraftTool: ToolConfig<GmailSendParams, GmailToolResponse> = {
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Email body content',
|
||||
},
|
||||
threadId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Thread ID to reply to (for threading)',
|
||||
},
|
||||
replyToMessageId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Gmail message ID to reply to - use the "id" field from Gmail Read results (not the RFC "messageId")',
|
||||
},
|
||||
cc: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
@@ -69,6 +82,8 @@ export const gmailDraftTool: ToolConfig<GmailSendParams, GmailToolResponse> = {
|
||||
to: params.to,
|
||||
subject: params.subject,
|
||||
body: params.body,
|
||||
threadId: params.threadId,
|
||||
replyToMessageId: params.replyToMessageId,
|
||||
cc: params.cc,
|
||||
bcc: params.bcc,
|
||||
attachments: params.attachments,
|
||||
|
||||
@@ -225,6 +225,7 @@ export const gmailReadTool: ToolConfig<GmailReadParams, GmailToolResponse> = {
|
||||
threadId: msg.threadId,
|
||||
subject: msg.subject,
|
||||
from: msg.from,
|
||||
to: msg.to,
|
||||
date: msg.date,
|
||||
})),
|
||||
},
|
||||
|
||||
@@ -28,7 +28,7 @@ export const gmailSendTool: ToolConfig<GmailSendParams, GmailToolResponse> = {
|
||||
},
|
||||
subject: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Email subject',
|
||||
},
|
||||
@@ -38,6 +38,19 @@ export const gmailSendTool: ToolConfig<GmailSendParams, GmailToolResponse> = {
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Email body content',
|
||||
},
|
||||
threadId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Thread ID to reply to (for threading)',
|
||||
},
|
||||
replyToMessageId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Gmail message ID to reply to - use the "id" field from Gmail Read results (not the RFC "messageId")',
|
||||
},
|
||||
cc: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
@@ -69,6 +82,8 @@ export const gmailSendTool: ToolConfig<GmailSendParams, GmailToolResponse> = {
|
||||
to: params.to,
|
||||
subject: params.subject,
|
||||
body: params.body,
|
||||
threadId: params.threadId,
|
||||
replyToMessageId: params.replyToMessageId,
|
||||
cc: params.cc,
|
||||
bcc: params.bcc,
|
||||
attachments: params.attachments,
|
||||
|
||||
@@ -11,8 +11,10 @@ export interface GmailSendParams extends BaseGmailParams {
|
||||
to: string
|
||||
cc?: string
|
||||
bcc?: string
|
||||
subject: string
|
||||
subject?: string
|
||||
body: string
|
||||
threadId?: string
|
||||
replyToMessageId?: string
|
||||
attachments?: UserFile[]
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,47 @@ import type {
|
||||
|
||||
export const GMAIL_API_BASE = 'https://gmail.googleapis.com/gmail/v1/users/me'
|
||||
|
||||
/**
|
||||
* Fetch original message headers for threading
|
||||
* @param messageId Gmail message ID to fetch headers from
|
||||
* @param accessToken Gmail access token
|
||||
* @returns Object containing threading headers (messageId, references, subject)
|
||||
*/
|
||||
export async function fetchThreadingHeaders(
|
||||
messageId: string,
|
||||
accessToken: string
|
||||
): Promise<{
|
||||
messageId?: string
|
||||
references?: string
|
||||
subject?: string
|
||||
}> {
|
||||
try {
|
||||
const messageResponse = await fetch(
|
||||
`${GMAIL_API_BASE}/messages/${messageId}?format=metadata&metadataHeaders=Message-ID&metadataHeaders=References&metadataHeaders=Subject`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (messageResponse.ok) {
|
||||
const messageData = await messageResponse.json()
|
||||
const headers = messageData.payload?.headers || []
|
||||
|
||||
return {
|
||||
messageId: headers.find((h: any) => h.name.toLowerCase() === 'message-id')?.value,
|
||||
references: headers.find((h: any) => h.name.toLowerCase() === 'references')?.value,
|
||||
subject: headers.find((h: any) => h.name.toLowerCase() === 'subject')?.value,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Continue without threading headers rather than failing
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
// Helper function to process a Gmail message
|
||||
export async function processMessage(
|
||||
message: GmailMessage,
|
||||
@@ -81,6 +122,7 @@ export function processMessageForSummary(message: GmailMessage): any {
|
||||
threadId: message?.threadId || '',
|
||||
subject: 'Unknown Subject',
|
||||
from: 'Unknown Sender',
|
||||
to: '',
|
||||
date: '',
|
||||
snippet: message?.snippet || '',
|
||||
}
|
||||
@@ -89,6 +131,7 @@ export function processMessageForSummary(message: GmailMessage): any {
|
||||
const headers = message.payload.headers || []
|
||||
const subject = headers.find((h) => h.name.toLowerCase() === 'subject')?.value || 'No Subject'
|
||||
const from = headers.find((h) => h.name.toLowerCase() === 'from')?.value || 'Unknown Sender'
|
||||
const to = headers.find((h) => h.name.toLowerCase() === 'to')?.value || ''
|
||||
const date = headers.find((h) => h.name.toLowerCase() === 'date')?.value || ''
|
||||
|
||||
return {
|
||||
@@ -96,6 +139,7 @@ export function processMessageForSummary(message: GmailMessage): any {
|
||||
threadId: message.threadId,
|
||||
subject,
|
||||
from,
|
||||
to,
|
||||
date,
|
||||
snippet: message.snippet || '',
|
||||
}
|
||||
@@ -230,7 +274,10 @@ export function createMessagesSummary(messages: any[]): string {
|
||||
messages.forEach((msg, index) => {
|
||||
summary += `${index + 1}. Subject: ${msg.subject}\n`
|
||||
summary += ` From: ${msg.from}\n`
|
||||
summary += ` To: ${msg.to}\n`
|
||||
summary += ` Date: ${msg.date}\n`
|
||||
summary += ` ID: ${msg.id}\n`
|
||||
summary += ` Thread ID: ${msg.threadId}\n`
|
||||
summary += ` Preview: ${msg.snippet}\n\n`
|
||||
})
|
||||
|
||||
@@ -255,6 +302,47 @@ export function base64UrlEncode(data: string | Buffer): string {
|
||||
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a simple text email message (without attachments)
|
||||
* @param params Email parameters including recipients, subject, body, and threading info
|
||||
* @returns Base64url encoded raw message
|
||||
*/
|
||||
export function buildSimpleEmailMessage(params: {
|
||||
to: string
|
||||
cc?: string | null
|
||||
bcc?: string | null
|
||||
subject?: string | null
|
||||
body: string
|
||||
inReplyTo?: string
|
||||
references?: string
|
||||
}): string {
|
||||
const { to, cc, bcc, subject, body, inReplyTo, references } = params
|
||||
const emailHeaders = [
|
||||
'Content-Type: text/plain; charset="UTF-8"',
|
||||
'MIME-Version: 1.0',
|
||||
`To: ${to}`,
|
||||
]
|
||||
|
||||
if (cc) {
|
||||
emailHeaders.push(`Cc: ${cc}`)
|
||||
}
|
||||
if (bcc) {
|
||||
emailHeaders.push(`Bcc: ${bcc}`)
|
||||
}
|
||||
|
||||
emailHeaders.push(`Subject: ${subject || ''}`)
|
||||
|
||||
if (inReplyTo) {
|
||||
emailHeaders.push(`In-Reply-To: ${inReplyTo}`)
|
||||
const referencesChain = references ? `${references} ${inReplyTo}` : inReplyTo
|
||||
emailHeaders.push(`References: ${referencesChain}`)
|
||||
}
|
||||
|
||||
emailHeaders.push('', body)
|
||||
const email = emailHeaders.join('\n')
|
||||
return Buffer.from(email).toString('base64url')
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a MIME multipart message with optional attachments
|
||||
* @param params Message parameters including recipients, subject, body, and attachments
|
||||
@@ -264,8 +352,10 @@ export interface BuildMimeMessageParams {
|
||||
to: string
|
||||
cc?: string
|
||||
bcc?: string
|
||||
subject: string
|
||||
subject?: string
|
||||
body: string
|
||||
inReplyTo?: string
|
||||
references?: string
|
||||
attachments?: Array<{
|
||||
filename: string
|
||||
mimeType: string
|
||||
@@ -274,11 +364,10 @@ export interface BuildMimeMessageParams {
|
||||
}
|
||||
|
||||
export function buildMimeMessage(params: BuildMimeMessageParams): string {
|
||||
const { to, cc, bcc, subject, body, attachments } = params
|
||||
const { to, cc, bcc, subject, body, inReplyTo, references, attachments } = params
|
||||
const boundary = generateBoundary()
|
||||
const messageParts: string[] = []
|
||||
|
||||
// Add headers
|
||||
messageParts.push(`To: ${to}`)
|
||||
if (cc) {
|
||||
messageParts.push(`Cc: ${cc}`)
|
||||
@@ -286,11 +375,21 @@ export function buildMimeMessage(params: BuildMimeMessageParams): string {
|
||||
if (bcc) {
|
||||
messageParts.push(`Bcc: ${bcc}`)
|
||||
}
|
||||
messageParts.push(`Subject: ${subject}`)
|
||||
messageParts.push(`Subject: ${subject || ''}`)
|
||||
|
||||
if (inReplyTo) {
|
||||
messageParts.push(`In-Reply-To: ${inReplyTo}`)
|
||||
}
|
||||
if (references) {
|
||||
const referencesChain = inReplyTo ? `${references} ${inReplyTo}` : references
|
||||
messageParts.push(`References: ${referencesChain}`)
|
||||
} else if (inReplyTo) {
|
||||
messageParts.push(`References: ${inReplyTo}`)
|
||||
}
|
||||
|
||||
messageParts.push('MIME-Version: 1.0')
|
||||
|
||||
if (attachments && attachments.length > 0) {
|
||||
// Multipart message with attachments
|
||||
messageParts.push(`Content-Type: multipart/mixed; boundary="${boundary}"`)
|
||||
messageParts.push('')
|
||||
messageParts.push(`--${boundary}`)
|
||||
@@ -300,7 +399,6 @@ export function buildMimeMessage(params: BuildMimeMessageParams): string {
|
||||
messageParts.push(body)
|
||||
messageParts.push('')
|
||||
|
||||
// Add each attachment
|
||||
for (const attachment of attachments) {
|
||||
messageParts.push(`--${boundary}`)
|
||||
messageParts.push(`Content-Type: ${attachment.mimeType}`)
|
||||
@@ -308,7 +406,6 @@ export function buildMimeMessage(params: BuildMimeMessageParams): string {
|
||||
messageParts.push('Content-Transfer-Encoding: base64')
|
||||
messageParts.push('')
|
||||
|
||||
// Split base64 content into 76-character lines (MIME standard)
|
||||
const base64Content = attachment.content.toString('base64')
|
||||
const lines = base64Content.match(/.{1,76}/g) || []
|
||||
messageParts.push(...lines)
|
||||
@@ -317,7 +414,6 @@ export function buildMimeMessage(params: BuildMimeMessageParams): string {
|
||||
|
||||
messageParts.push(`--${boundary}--`)
|
||||
} else {
|
||||
// Simple text message without attachments
|
||||
messageParts.push('Content-Type: text/plain; charset="UTF-8"')
|
||||
messageParts.push('MIME-Version: 1.0')
|
||||
messageParts.push('')
|
||||
|
||||
Reference in New Issue
Block a user