feat(gmail): add reply, forward, attachment, and message management operations#44
Conversation
…perations Implements GDRIVE-12: Complete human-parity email operations including: - replyToMessage with proper MIME threading headers - replyAllToMessage with recipient deduplication - forwardMessage with quoted original content - listAttachments, downloadAttachment, sendWithAttachments - trashMessage, untrashMessage, deleteMessage (with safety guard) - markAsRead, markAsUnread, archiveMessage Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis pull request expands the Gmail SDK module with comprehensive support for message threading (reply, reply-all), forwarding, attachment handling, and message management operations. Twelve new functions are introduced with corresponding type definitions, SDK runtime integration, and extensive test coverage. The module version is bumped from 3.2.0 to 3.3.0. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Runtime
participant Gmail API
participant Message Context
participant Cache
Client->>Runtime: replyToMessage(messageId, body)
Runtime->>Gmail API: get(userId:'me', id:messageId)
Gmail API-->>Runtime: original message with headers
Runtime->>Message Context: extract threading headers<br/>(Message-ID, References)
Message Context-->>Runtime: inReplyTo, references, threadId
Runtime->>Runtime: build email with<br/>In-Reply-To & References
Runtime->>Gmail API: send(raw message)
Gmail API-->>Runtime: messageId, threadId, labelIds
Runtime->>Cache: clear('gmail:list')
Cache-->>Runtime: cleared
Runtime-->>Client: { messageId, threadId, labelIds, message }
sequenceDiagram
participant Client
participant Runtime
participant Validator
participant MIME Builder
participant Gmail API
participant Cache
Client->>Runtime: sendWithAttachments(to, subject, attachments)
Runtime->>Validator: validate recipients & attachments
Validator-->>Runtime: validated options
Runtime->>MIME Builder: buildMultipartMessage(headers, body, attachments)
MIME Builder->>MIME Builder: encode attachments to base64
MIME Builder->>MIME Builder: construct multipart/mixed<br/>with boundary
MIME Builder-->>Runtime: RFC 2822 message
Runtime->>Runtime: encodeToBase64Url(message)
Runtime->>Gmail API: send(raw:encoded_message)
Gmail API-->>Runtime: messageId, threadId, labelIds
Runtime->>Cache: clear('gmail:list')<br/>clear('gmail:search')
Cache-->>Runtime: cleared
Runtime-->>Client: { messageId, threadId, labelIds, message }
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
gdrive-mcp | 64fae91 | Commit Preview URL Branch Preview URL |
Mar 04 2026, 01:45 AM |
📊 Type Coverage ReportType Coverage: 98.59% This PR's TypeScript type coverage analysis is complete. |
🔒 Security Scan SummaryGenerated on: Wed Mar 4 01:46:29 UTC 2026 Scan Results
Summary
Recommendations
Security report generated by Claude Code |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 64fae91097
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| */ | ||
| function parseEmailList(value: string | undefined): string[] { | ||
| if (!value || value.trim() === '') {return [];} | ||
| return value.split(',').map(e => e.trim()).filter(e => e.length > 0); |
There was a problem hiding this comment.
Parse recipient lists with RFC-aware logic
replyAllToMessage builds recipients by splitting header values on commas, but valid RFC 5322 addresses can contain commas inside quoted display names (for example "Doe, John" <john@example.com>). In those cases this split produces broken fragments that later fail recipient validation, so reply-all can fail on legitimate messages. Use a real address parser (or Gmail-parsed addresses) instead of naive comma splitting.
Useful? React with 👍 / 👎.
| lines.push('Content-Transfer-Encoding: base64'); | ||
| lines.push(''); | ||
| // Break base64 data into 76-char lines per RFC 2045 | ||
| const b64 = attachment.data.replace(/[^A-Za-z0-9+/=]/g, ''); |
There was a problem hiding this comment.
Normalize base64url attachment data before MIME encoding
buildMultipartMessage removes any attachment characters outside [A-Za-z0-9+/=], which drops - and _ from base64url payloads instead of converting them. Since downloadAttachment returns Gmail data in base64url form, piping downloaded content into sendWithAttachments can silently corrupt files. Convert base64url to standard base64 (-→+, _→/) rather than stripping those characters.
Useful? React with 👍 / 👎.
| lines.push('Content-Transfer-Encoding: quoted-printable'); | ||
| lines.push(''); | ||
| lines.push(body); |
There was a problem hiding this comment.
Encode multipart body to match declared transfer encoding
The text part is marked as Content-Transfer-Encoding: quoted-printable, but the code writes the raw body string without quoted-printable encoding. For bodies containing non-ASCII bytes or quoted-printable metacharacters (especially =), some mail clients will decode the content incorrectly and display garbled text. Either encode the body as quoted-printable or declare a transfer encoding that matches the raw content.
Useful? React with 👍 / 👎.
Performance Comparison ReportOperation Performance
Memory Usage
Summary
Performance report generated by Claude Code |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (4)
src/modules/gmail/attachments.ts (2)
210-213: Validation results are discarded.
validateAndSanitizeRecipientsreturns sanitized email addresses, but the return values are not used. WhilebuildMultipartMessagewill sanitize again internally, this results in redundant sanitization. Consider either using the sanitized values or switching to a validation-only function.Option: Use sanitized values directly
// Validate recipients (throws if any invalid) - validateAndSanitizeRecipients(to, 'to'); - if (cc && cc.length > 0) {validateAndSanitizeRecipients(cc, 'cc');} - if (bcc && bcc.length > 0) {validateAndSanitizeRecipients(bcc, 'bcc');} + const sanitizedTo = validateAndSanitizeRecipients(to, 'to'); + const sanitizedCc = cc && cc.length > 0 ? validateAndSanitizeRecipients(cc, 'cc') : undefined; + const sanitizedBcc = bcc && bcc.length > 0 ? validateAndSanitizeRecipients(bcc, 'bcc') : undefined; // Build params without passing undefined to optional fields (exactOptionalPropertyTypes) const messageParams: Parameters<typeof buildMultipartMessage>[0] = { - to, + to: sanitizedTo, subject, body, attachments: attachments || [], }; - if (cc && cc.length > 0) {messageParams.cc = cc;} - if (bcc && bcc.length > 0) {messageParams.bcc = bcc;} + if (sanitizedCc) {messageParams.cc = sanitizedCc;} + if (sanitizedBcc) {messageParams.bcc = sanitizedBcc;}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/modules/gmail/attachments.ts` around lines 210 - 213, validateAndSanitizeRecipients returns sanitized addresses but its results are ignored, causing redundant sanitization in buildMultipartMessage; update the call sites so you capture and use the returned sanitized arrays (e.g., to = validateAndSanitizeRecipients(to, 'to'); and likewise for cc and bcc) or replace the calls with a validation-only helper if you intentionally want no mutation—adjust references to to, cc, bcc before passing them into buildMultipartMessage so the sanitized values are used.
154-161: Consider throwing an error when attachment metadata is not found.If
attachmentIddoesn't match any attachment in the message,attachmentMetawill beundefinedand the download proceeds with the API call succeeding but returning empty filename/mimeType. This could lead to confusing results for the caller.Proposed fix to fail fast
const attachmentMeta = allAttachments.find(a => a.attachmentId === attachmentId); + if (!attachmentMeta) { + throw new Error(`Attachment ${attachmentId} not found in message ${messageId}`); + } // Download the attachment content const response = await context.gmail.users.messages.attachments.get({🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/modules/gmail/attachments.ts` around lines 154 - 161, If attachmentMeta (from allAttachments.find(...)) is undefined, fail fast by throwing a descriptive error before calling context.gmail.users.messages.attachments.get; check the result of the find for the given attachmentId (and include messageId/attachmentId in the message) so callers get a clear failure instead of proceeding to download with empty filename/mimeType.src/modules/gmail/forward.ts (1)
34-40: Redundant conditional branch.Lines 36-38 check if
mimeTypeistext/plainortext/htmland decode, but line 39 decodes unconditionally anyway when the condition is false. The entire if-else can be simplified.Simplified version
// Simple single-part message if (payload.body?.data) { - const mimeType = payload.mimeType || ''; - if (mimeType === 'text/plain' || mimeType === 'text/html') { - return decode(payload.body.data); - } return decode(payload.body.data); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/modules/gmail/forward.ts` around lines 34 - 40, The conditional that checks mimeType in src/modules/gmail/forward.ts is redundant: inside the block where payload.body?.data exists the code decodes for both the if and the else path. Update the code inside the function handling payload to remove the unnecessary mimeType branch and simply return decode(payload.body.data) when payload.body?.data is present (remove the unused mimeType variable and the if block around it); keep the surrounding null/undefined checks intact so other behavior is unchanged.src/modules/gmail/types.ts (1)
551-559: Use a literaltruetype forsafetyAcknowledgedto enforce the safety contract at compile time.Currently at line 558,
booleanallowsfalsevalues to pass type checking. While runtime validation at line 129 ofmanage.tsrejects falsy values with an error, using a literaltruetype would catch this mistake during development rather than at runtime. For an irreversible operation, encoding the requirement in the type system prevents unsafe calls from being written in the first place.Suggested fix
export interface DeleteMessageOptions { /** The message ID */ id: string; /** * Must be true to confirm permanent deletion. * This operation cannot be undone — use trashMessage() for recoverable deletion. */ - safetyAcknowledged: boolean; + safetyAcknowledged: true; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/modules/gmail/types.ts` around lines 551 - 559, Change the DeleteMessageOptions interface to require the literal true type for safetyAcknowledged (replace boolean with the literal true) so callers must pass true at compile time; update any call sites that construct DeleteMessageOptions (e.g., where delete message requests are created) to pass the literal true value and adjust any tests or helpers that currently pass false or a boolean variable; leave runtime validation in manage.ts unchanged but rely on the type-level enforcement provided by the modified DeleteMessageOptions.safetyAcknowledged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/modules/gmail/manage.ts`:
- Around line 52-54: The cache invalidation after message mutations currently
only clears per-message and list keys (calls to context.cacheManager.invalidate
like invalidate(`gmail:getMessage:${id}`) and invalidate('gmail:list')); add an
additional invalidate('gmail:search') call in the same mutation code paths
(trash/untrash/delete/read-state/archive handlers) so search results are cleared
too; update each block where those two invalidates exist (the occurrences around
the existing invalidate calls and the other instances noted) to call
context.cacheManager.invalidate('gmail:search') immediately alongside the other
invalidates.
In `@src/modules/gmail/reply.ts`:
- Around line 77-113: The code currently selects the reply recipient from the
original "From" header only; update the header extraction and recipient
selection to prefer "Reply-To" and fall back to "From" (use findHeader(headers,
'Reply-To') then findHeader(headers, 'From')), update the metadataHeaders array
to include 'Reply-To', and set messageParams.to to use the chosen replyRecipient
(e.g., replyRecipient variable) instead of originalFrom; additionally add a
guard that throws or returns a clear error if neither Reply-To nor From is
present to avoid sending with an invalid recipient (refer to
originalMessageId/originalReferences handling, buildEmailMessage, and
replySubject to keep threading and subject logic unchanged).
- Around line 26-28: parseEmailList currently splits on commas which breaks
quoted display names (e.g. "Doe, Jane" <jane@example.com>) and can corrupt
dedupe/self-exclusion and recipient lists; update parseEmailList to parse
RFC-5322 style address headers instead of simple split: either use a
CSV/quoted-string aware split or implement/extract addresses by matching address
tokens (look for angle-bracketed addresses like <...> and fallback to bare
addresses) so you return only canonical email addresses for
dedupe/self-exclusion logic; locate and change the parseEmailList function and
ensure callers that rely on its output (dedupe/self-exclusion) receive correctly
parsed addresses.
In `@src/modules/gmail/utils.ts`:
- Around line 248-252: The code writes the body verbatim but sets
Content-Transfer-Encoding: quoted-printable; either actually quoted-printable
encode the body or change the header to a truthful encoding. Fix by updating the
block that uses isHtml/body/lines (the lines.push('Content-Transfer-Encoding:
...') call) to either 1) replace that header with 'Content-Transfer-Encoding:
8bit' when you will send raw UTF-8 body, or 2) import/use a quoted-printable
encoder and run body = quotedPrintableEncode(body) before pushing the body and
keep the header as quoted-printable; ensure the chosen approach is applied for
both HTML and plain branches so the header matches the body content.
---
Nitpick comments:
In `@src/modules/gmail/attachments.ts`:
- Around line 210-213: validateAndSanitizeRecipients returns sanitized addresses
but its results are ignored, causing redundant sanitization in
buildMultipartMessage; update the call sites so you capture and use the returned
sanitized arrays (e.g., to = validateAndSanitizeRecipients(to, 'to'); and
likewise for cc and bcc) or replace the calls with a validation-only helper if
you intentionally want no mutation—adjust references to to, cc, bcc before
passing them into buildMultipartMessage so the sanitized values are used.
- Around line 154-161: If attachmentMeta (from allAttachments.find(...)) is
undefined, fail fast by throwing a descriptive error before calling
context.gmail.users.messages.attachments.get; check the result of the find for
the given attachmentId (and include messageId/attachmentId in the message) so
callers get a clear failure instead of proceeding to download with empty
filename/mimeType.
In `@src/modules/gmail/forward.ts`:
- Around line 34-40: The conditional that checks mimeType in
src/modules/gmail/forward.ts is redundant: inside the block where
payload.body?.data exists the code decodes for both the if and the else path.
Update the code inside the function handling payload to remove the unnecessary
mimeType branch and simply return decode(payload.body.data) when
payload.body?.data is present (remove the unused mimeType variable and the if
block around it); keep the surrounding null/undefined checks intact so other
behavior is unchanged.
In `@src/modules/gmail/types.ts`:
- Around line 551-559: Change the DeleteMessageOptions interface to require the
literal true type for safetyAcknowledged (replace boolean with the literal true)
so callers must pass true at compile time; update any call sites that construct
DeleteMessageOptions (e.g., where delete message requests are created) to pass
the literal true value and adjust any tests or helpers that currently pass false
or a boolean variable; leave runtime validation in manage.ts unchanged but rely
on the type-level enforcement provided by the modified
DeleteMessageOptions.safetyAcknowledged.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
src/__tests__/sdk/runtime-rate-limiter-scope.test.tssrc/modules/gmail/__tests__/attachments.test.tssrc/modules/gmail/__tests__/forward.test.tssrc/modules/gmail/__tests__/manage.test.tssrc/modules/gmail/__tests__/reply.test.tssrc/modules/gmail/attachments.tssrc/modules/gmail/forward.tssrc/modules/gmail/index.tssrc/modules/gmail/manage.tssrc/modules/gmail/reply.tssrc/modules/gmail/types.tssrc/modules/gmail/utils.tssrc/sdk/runtime.tssrc/sdk/spec.tssrc/sdk/types.ts
| await context.cacheManager.invalidate(`gmail:getMessage:${id}`); | ||
| await context.cacheManager.invalidate('gmail:list'); | ||
|
|
There was a problem hiding this comment.
Invalidate gmail:search after message mutations to prevent stale reads.
At Line 52/53 and the equivalent blocks in other operations, only per-message/list caches are invalidated. Search results can stay stale after trash/untrash/delete/read-state/archive updates.
Suggested fix
@@
await context.cacheManager.invalidate(`gmail:getMessage:${id}`);
await context.cacheManager.invalidate('gmail:list');
+ await context.cacheManager.invalidate('gmail:search');
@@
await context.cacheManager.invalidate(`gmail:getMessage:${id}`);
await context.cacheManager.invalidate('gmail:list');
+ await context.cacheManager.invalidate('gmail:search');
@@
await context.cacheManager.invalidate(`gmail:getMessage:${id}`);
await context.cacheManager.invalidate('gmail:list');
+ await context.cacheManager.invalidate('gmail:search');
@@
await context.cacheManager.invalidate(`gmail:getMessage:${id}`);
await context.cacheManager.invalidate('gmail:list');
+ await context.cacheManager.invalidate('gmail:search');
@@
await context.cacheManager.invalidate(`gmail:getMessage:${id}`);
await context.cacheManager.invalidate('gmail:list');
+ await context.cacheManager.invalidate('gmail:search');
@@
await context.cacheManager.invalidate(`gmail:getMessage:${id}`);
await context.cacheManager.invalidate('gmail:list');
+ await context.cacheManager.invalidate('gmail:search');Also applies to: 91-93, 142-144, 184-186, 227-229, 272-274
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/modules/gmail/manage.ts` around lines 52 - 54, The cache invalidation
after message mutations currently only clears per-message and list keys (calls
to context.cacheManager.invalidate like invalidate(`gmail:getMessage:${id}`) and
invalidate('gmail:list')); add an additional invalidate('gmail:search') call in
the same mutation code paths (trash/untrash/delete/read-state/archive handlers)
so search results are cleared too; update each block where those two invalidates
exist (the occurrences around the existing invalidate calls and the other
instances noted) to call context.cacheManager.invalidate('gmail:search')
immediately alongside the other invalidates.
| function parseEmailList(value: string | undefined): string[] { | ||
| if (!value || value.trim() === '') {return [];} | ||
| return value.split(',').map(e => e.trim()).filter(e => e.length > 0); |
There was a problem hiding this comment.
parseEmailList breaks valid quoted address headers containing commas.
Line 28 uses split(','), which mis-parses values like "Doe, Jane" <jane@example.com>, team@example.com. That can corrupt dedupe/self-exclusion and send to malformed recipients.
Suggested fix
function parseEmailList(value: string | undefined): string[] {
if (!value || value.trim() === '') {return [];}
- return value.split(',').map(e => e.trim()).filter(e => e.length > 0);
+ const result: string[] = [];
+ let current = '';
+ let inQuotes = false;
+ let angleDepth = 0;
+
+ for (const ch of value) {
+ if (ch === '"' && angleDepth === 0) {inQuotes = !inQuotes;}
+ if (!inQuotes) {
+ if (ch === '<') {angleDepth += 1;}
+ if (ch === '>') {angleDepth = Math.max(0, angleDepth - 1);}
+ if (ch === ',' && angleDepth === 0) {
+ const token = current.trim();
+ if (token) {result.push(token);}
+ current = '';
+ continue;
+ }
+ }
+ current += ch;
+ }
+
+ const token = current.trim();
+ if (token) {result.push(token);}
+ return result;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/modules/gmail/reply.ts` around lines 26 - 28, parseEmailList currently
splits on commas which breaks quoted display names (e.g. "Doe, Jane"
<jane@example.com>) and can corrupt dedupe/self-exclusion and recipient lists;
update parseEmailList to parse RFC-5322 style address headers instead of simple
split: either use a CSV/quoted-string aware split or implement/extract addresses
by matching address tokens (look for angle-bracketed addresses like <...> and
fallback to bare addresses) so you return only canonical email addresses for
dedupe/self-exclusion logic; locate and change the parseEmailList function and
ensure callers that rely on its output (dedupe/self-exclusion) receive correctly
parsed addresses.
| const originalResponse = await context.gmail.users.messages.get({ | ||
| userId: 'me', | ||
| id: messageId, | ||
| format: 'metadata', | ||
| metadataHeaders: ['From', 'Subject', 'Message-ID', 'References', 'To'], | ||
| }); | ||
|
|
||
| const originalData = originalResponse.data; | ||
| const headers = originalData.payload?.headers || []; | ||
|
|
||
| const originalFrom = findHeader(headers, 'From'); | ||
| const originalSubject = findHeader(headers, 'Subject'); | ||
| const originalMessageId = findHeader(headers, 'Message-ID'); | ||
| const originalReferences = findHeader(headers, 'References'); | ||
| const threadId = originalData.threadId || ''; | ||
|
|
||
| // Build threading headers from the original message's MIME Message-ID | ||
| let inReplyTo: string | undefined; | ||
| let references: string | undefined; | ||
|
|
||
| if (originalMessageId) { | ||
| inReplyTo = originalMessageId; | ||
| // References = original References + original Message-ID | ||
| references = originalReferences | ||
| ? `${originalReferences} ${originalMessageId}` | ||
| : originalMessageId; | ||
| } | ||
|
|
||
| // Determine subject | ||
| const subject = replySubject(originalSubject); | ||
|
|
||
| // Build params without passing undefined to optional fields (exactOptionalPropertyTypes) | ||
| const messageParams: Parameters<typeof buildEmailMessage>[0] = { | ||
| to: [originalFrom], | ||
| subject, | ||
| body, | ||
| }; |
There was a problem hiding this comment.
Use Reply-To (fallback From) for reply recipient selection.
At Line 81 and Line 197, metadataHeaders omits Reply-To. At Line 110 and Line 224, replies are addressed from From only. For list/automated mail, this can route replies to the wrong mailbox. Also add a guard when neither header is usable so we fail with a clear error instead of sending an invalid recipient.
Suggested fix
@@
- metadataHeaders: ['From', 'Subject', 'Message-ID', 'References', 'To'],
+ metadataHeaders: ['Reply-To', 'From', 'Subject', 'Message-ID', 'References', 'To'],
@@
+ const originalReplyTo = findHeader(headers, 'Reply-To');
const originalFrom = findHeader(headers, 'From');
@@
+ const replyTarget = (originalReplyTo || originalFrom).trim();
+ if (!replyTarget) {
+ throw new Error('Cannot determine reply recipient: missing Reply-To/From header');
+ }
@@
- to: [originalFrom],
+ to: [replyTarget],
@@
- metadataHeaders: ['From', 'To', 'Cc', 'Subject', 'Message-ID', 'References'],
+ metadataHeaders: ['Reply-To', 'From', 'To', 'Cc', 'Subject', 'Message-ID', 'References'],
@@
+ const originalReplyTo = findHeader(headers, 'Reply-To');
const originalFrom = findHeader(headers, 'From');
@@
- toAddresses.push(originalFrom);
+ toAddresses.push((originalReplyTo || originalFrom).trim());
@@
- const bare = extractEmailAddress(addr).toLowerCase();
+ const bare = extractEmailAddress(addr).toLowerCase();
+ if (!bare) {continue;}Also applies to: 193-226
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/modules/gmail/reply.ts` around lines 77 - 113, The code currently selects
the reply recipient from the original "From" header only; update the header
extraction and recipient selection to prefer "Reply-To" and fall back to "From"
(use findHeader(headers, 'Reply-To') then findHeader(headers, 'From')), update
the metadataHeaders array to include 'Reply-To', and set messageParams.to to use
the chosen replyRecipient (e.g., replyRecipient variable) instead of
originalFrom; additionally add a guard that throws or returns a clear error if
neither Reply-To nor From is present to avoid sending with an invalid recipient
(refer to originalMessageId/originalReferences handling, buildEmailMessage, and
replySubject to keep threading and subject logic unchanged).
| lines.push(`Content-Type: ${isHtml ? 'text/html' : 'text/plain'}; charset="UTF-8"`); | ||
| lines.push('Content-Transfer-Encoding: quoted-printable'); | ||
| lines.push(''); | ||
| lines.push(body); | ||
| lines.push(''); |
There was a problem hiding this comment.
Body part declares quoted-printable encoding but content is not encoded.
Line 249 sets Content-Transfer-Encoding: quoted-printable but the body is written verbatim on line 251. If the body contains characters that require quoted-printable encoding (e.g., lines >76 chars, non-ASCII, = signs), the message may be malformed. Either apply proper quoted-printable encoding or use Content-Transfer-Encoding: 8bit (or 7bit for ASCII-only).
Proposed fix using 8bit encoding (simpler)
lines.push(`--${boundary}`);
lines.push(`Content-Type: ${isHtml ? 'text/html' : 'text/plain'}; charset="UTF-8"`);
- lines.push('Content-Transfer-Encoding: quoted-printable');
+ lines.push('Content-Transfer-Encoding: 8bit');
lines.push('');
lines.push(body);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| lines.push(`Content-Type: ${isHtml ? 'text/html' : 'text/plain'}; charset="UTF-8"`); | |
| lines.push('Content-Transfer-Encoding: quoted-printable'); | |
| lines.push(''); | |
| lines.push(body); | |
| lines.push(''); | |
| lines.push(`Content-Type: ${isHtml ? 'text/html' : 'text/plain'}; charset="UTF-8"`); | |
| lines.push('Content-Transfer-Encoding: 8bit'); | |
| lines.push(''); | |
| lines.push(body); | |
| lines.push(''); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/modules/gmail/utils.ts` around lines 248 - 252, The code writes the body
verbatim but sets Content-Transfer-Encoding: quoted-printable; either actually
quoted-printable encode the body or change the header to a truthful encoding.
Fix by updating the block that uses isHtml/body/lines (the
lines.push('Content-Transfer-Encoding: ...') call) to either 1) replace that
header with 'Content-Transfer-Encoding: 8bit' when you will send raw UTF-8 body,
or 2) import/use a quoted-printable encoder and run body =
quotedPrintableEncode(body) before pushing the body and keep the header as
quoted-printable; ensure the chosen approach is applied for both HTML and plain
branches so the header matches the body content.
Summary
Implements GDRIVE-12: Complete human-parity email operations for the Gmail service.
Message-ID,In-Reply-To, andReferencesheaders (fixes unreliable threading with mailing lists/ConvertKit)safetyAcknowledged: truerequired for permanent delete)Key Technical Decisions
replyToMessagefetches original withformat: 'metadata'for efficient threading header extractionbuildMultipartMessageutility in utils.ts for multipart/mixed MIME encodingTest Plan
Resolves GDRIVE-12
🤖 Generated with Claude Code
Summary by CodeRabbit