Skip to content

fix: forward SMTP attachments - #405

Merged
KMKoushik merged 5 commits into
mainfrom
fix/smtp-forward-attachments
May 18, 2026
Merged

fix: forward SMTP attachments#405
KMKoushik merged 5 commits into
mainfrom
fix/smtp-forward-attachments

Conversation

@KMKoushik

@KMKoushik KMKoushik commented May 18, 2026

Copy link
Copy Markdown
Member

Summary

  • forward mailparser attachments from the SMTP server to the useSend email API
  • encode attachment buffers as base64 content expected by the API
  • provide fallback attachment filenames when SMTP messages omit them

Verification

  • pnpm --filter smtp-server exec tsc --noEmit
  • pnpm exec prettier --check apps/smtp-server/src/server.ts

Closes #399

Summary by CodeRabbit

  • New Features
    • Email attachments are now detected and forwarded with messages. Each attachment is sent with a filename (fallback name supplied when missing) and base64-encoded content. If a message has no attachments, no attachments field is included. This ensures attachments are preserved when delivering emails.

Review Change Stack

Entire-Checkpoint: 7d7cb24de113
@vercel

vercel Bot commented May 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
unsend-marketing Ready Ready Preview, Comment May 18, 2026 6:09am

@coderabbitai

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

The SMTP server now extracts email attachments parsed by mailparser and forwards them to the useSend API. The emailObject payload construction adds an attachments property that transforms each attachment from the parsed email into a standardized shape with base64-encoded content and a filename (generated as a fallback when missing). When no attachments exist, the field is set to undefined.

Possibly related PRs

  • usesend/useSend#258: Modifies email queue persistence to clear the attachments column after send, creating a lifecycle connection with this PR's attachment handling.

Suggested labels

codex

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: forward SMTP attachments' clearly and concisely describes the main change: enabling SMTP attachment forwarding.
Linked Issues check ✅ Passed The PR successfully implements the core objective from #399: including parsed SMTP attachments in the API payload with base64-encoded content and fallback filenames.
Out of Scope Changes check ✅ Passed All changes are directly related to the objective of forwarding SMTP attachments; no unrelated modifications are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented May 18, 2026

Copy link
Copy Markdown

Deploying usesend with  Cloudflare Pages  Cloudflare Pages

Latest commit: e5ffda8
Status: ✅  Deploy successful!
Preview URL: https://998a15dd.usesend.pages.dev
Branch Preview URL: https://fix-smtp-forward-attachments.usesend.pages.dev

View logs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/smtp-server/src/server.ts (1)

38-42: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Redact attachment content from error logs.

The error logging now includes the full emailData which contains base64-encoded attachment content (lines 102-108). This creates both privacy and performance concerns:

  1. Privacy risk: Attachments may contain sensitive or confidential data (PII, documents, images) that should not be logged.
  2. Log bloat: Large attachments (approaching the 10MB limit at line 129) will create enormous log entries, making logs difficult to manage and consuming excessive storage.
  3. Compliance: Logging user data without redaction conflicts with the repository-wide PII redaction policy.
🔒 Proposed fix to redact attachments from logs
     if (!response.ok) {
       const errorData = await response.text();
+      const redactedEmailData = { ...emailData };
+      if (redactedEmailData.attachments) {
+        redactedEmailData.attachments = redactedEmailData.attachments.map(
+          (att: any) => ({ filename: att.filename, size: att.content?.length || 0 })
+        );
+      }
       console.error(
         "useSend API error response: error:",
         JSON.stringify(errorData, null, 4),
-        `\nemail data: ${emailDataText}`,
+        `\nemail data: ${JSON.stringify(redactedEmailData, null, 2)}`,
       );

Based on learnings: Actionable guideline: enforce PII data redaction across the codebase by centralizing with a Pino serializer rather than ad-hoc fixes. Treat logging of full email addresses at info level as a repository-wide concern and audit all ts files that log user identifiers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/smtp-server/src/server.ts` around lines 38 - 42, The error log currently
prints full emailData (via emailDataText/JSON.stringify) which includes base64
attachments; update the logging in server.ts where console.error is called (the
useSend API error path) to redact attachment bodies before stringifying by
removing or replacing the attachment content field(s) (e.g., set attachment.data
or attachment.content to "<redacted_attachment>" or omit the attachment payload
entirely) and instead include non-sensitive metadata (filename, contentType,
size); prefer using a centralized redaction helper or pino serializer for
emailData to ensure attachments are never logged and replace the current
JSON.stringify(emailData, ...) with the sanitized version.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/smtp-server/src/server.ts`:
- Around line 102-108: The attachments block assumes parsed.attachments exists
and uses parsed.attachments.length directly; update the guard to use optional
chaining and safe mapping: check parsed.attachments?.length or equivalent and
only map when parsed.attachments is defined (e.g., use
parsed.attachments?.length ? parsed.attachments.map(...) : undefined or map over
parsed.attachments ?? [] and convert to undefined if empty), referring to the
attachments mapping in server.ts where the parsed variable is used.

---

Outside diff comments:
In `@apps/smtp-server/src/server.ts`:
- Around line 38-42: The error log currently prints full emailData (via
emailDataText/JSON.stringify) which includes base64 attachments; update the
logging in server.ts where console.error is called (the useSend API error path)
to redact attachment bodies before stringifying by removing or replacing the
attachment content field(s) (e.g., set attachment.data or attachment.content to
"<redacted_attachment>" or omit the attachment payload entirely) and instead
include non-sensitive metadata (filename, contentType, size); prefer using a
centralized redaction helper or pino serializer for emailData to ensure
attachments are never logged and replace the current JSON.stringify(emailData,
...) with the sanitized version.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d3d129e2-862c-475e-ad5c-287230dc3daa

📥 Commits

Reviewing files that changed from the base of the PR and between 2eca312 and dcca2ec.

📒 Files selected for processing (1)
  • apps/smtp-server/src/server.ts

Comment thread apps/smtp-server/src/server.ts
Entire-Checkpoint: 0b350a0d62e6

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/smtp-server/src/server.ts (1)

127-127: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Redact email addresses from info-level logs.

This line logs full email addresses (emailObject.to) at info level, which violates the repository's PII handling policy.

🔒 Recommended fix
-      .then(() => console.log("Email sent successfully to: ", emailObject.to))
+      .then(() => console.log("Email sent successfully"))

Based on learnings: The repository standard is to enforce PII data redaction across the codebase by centralizing with a Pino serializer rather than ad-hoc fixes, treating logging of full email addresses at info level as a repository-wide concern.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/smtp-server/src/server.ts` at line 127, This logs full email addresses
via console.log(emailObject.to) which exposes PII; replace the ad‑hoc
console.log call in apps/smtp-server/src/server.ts with the project logger
(e.g., processLogger or the module's pino logger) and emit a non-PII message
(e.g., "Email sent") or pass a redacted recipient value; do not log raw
emailObject.to. Ensure the change relies on the centralized Pino
serializer/redaction policy rather than embedding a manual redact routine so
email addresses are consistently redacted across the service.
♻️ Duplicate comments (1)
apps/smtp-server/src/server.ts (1)

117-122: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Guard against undefined parsed.attachments with optional chaining.

The code accesses parsed.attachments.length directly without checking if attachments is defined. According to the @types/mailparser type definitions, the attachments property in ParsedMail is optional (attachments?: Attachment[]) and may be undefined. This will cause a runtime TypeError if no attachments are present.

🛡️ Recommended fix
       attachments:
-        parsed.attachments.length > 0
+        parsed.attachments?.length
           ? parsed.attachments.map((attachment, index) => ({
               filename: attachment.filename || `attachment-${index + 1}`,
               content: attachment.content.toString("base64"),
             }))
           : undefined,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/smtp-server/src/server.ts` around lines 117 - 122, The code accesses
parsed.attachments.length directly and can throw if attachments is undefined;
update the block that builds the attachments array (the parsed.attachments
mapping logic in server.ts) to use optional chaining and/or a safe check (e.g.,
parsed.attachments?.length or parsed.attachments?.map) so it only maps when
attachments exists, and return undefined otherwise; ensure the mapping still
applies the filename fallback (`attachment.filename || \`attachment-${index +
1}\``) and base64 encoding of attachment.content when present.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/smtp-server/src/server.ts`:
- Around line 38-55: The log currently prints emailDataForLog (constructed from
emailData and its attachments) directly in the console.error call, which leaks
PII in the to/from/replyTo fields; update the construction of emailDataForLog
before the console.error so that any addresses in emailData.to, emailData.from,
and emailData.replyTo are redacted (e.g., replace local-part with **** or only
keep domain) while preserving other non-PII fields and the existing attachment
sanitization; modify the logic that builds emailDataForLog (the ternary that
maps attachments) to also call a small sanitizer function applied to those three
fields and use the sanitized emailDataForLog in the console.error call (or,
preferably, switch to structured logging/serializer if a Pino serializer is
available in the codebase).

---

Outside diff comments:
In `@apps/smtp-server/src/server.ts`:
- Line 127: This logs full email addresses via console.log(emailObject.to) which
exposes PII; replace the ad‑hoc console.log call in
apps/smtp-server/src/server.ts with the project logger (e.g., processLogger or
the module's pino logger) and emit a non-PII message (e.g., "Email sent") or
pass a redacted recipient value; do not log raw emailObject.to. Ensure the
change relies on the centralized Pino serializer/redaction policy rather than
embedding a manual redact routine so email addresses are consistently redacted
across the service.

---

Duplicate comments:
In `@apps/smtp-server/src/server.ts`:
- Around line 117-122: The code accesses parsed.attachments.length directly and
can throw if attachments is undefined; update the block that builds the
attachments array (the parsed.attachments mapping logic in server.ts) to use
optional chaining and/or a safe check (e.g., parsed.attachments?.length or
parsed.attachments?.map) so it only maps when attachments exists, and return
undefined otherwise; ensure the mapping still applies the filename fallback
(`attachment.filename || \`attachment-${index + 1}\``) and base64 encoding of
attachment.content when present.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c3698b68-7150-4f32-9afb-08ac3c5ebb8e

📥 Commits

Reviewing files that changed from the base of the PR and between dcca2ec and 31e51d2.

📒 Files selected for processing (1)
  • apps/smtp-server/src/server.ts

Comment thread apps/smtp-server/src/server.ts Outdated
Entire-Checkpoint: 05a0a1779c5e
@KMKoushik

Copy link
Copy Markdown
Member Author

Addressed the latest CodeRabbit review notes:

  • Confirmed the parsed.attachments undefined warning is not applicable for the pinned package versions: @types/mailparser@3.4.5 types it as required and mailparser@3.9.8 initializes it to [].
  • Fixed attachment body redaction in API error logs in 31e51d2.
  • Fixed email address redaction/removal in SMTP logs in c3e6657: API error logs redact to/from/replyTo, and the success log no longer includes the recipient address.

Verification rerun:

  • pnpm --filter smtp-server exec tsc --noEmit
  • pnpm exec prettier --check apps/smtp-server/src/server.ts

KMKoushik added 2 commits May 18, 2026 16:07
This reverts commit c3e6657.

Entire-Checkpoint: 0225a4ef8dee
This reverts commit 31e51d2.

Entire-Checkpoint: ee934de340c7
@KMKoushik

Copy link
Copy Markdown
Member Author

Scope correction: reverted the logging/redaction follow-up changes. The PR diff is now limited to issue #399 only: forwarding parsed SMTP attachments to the API as base64 attachment payloads.

Verification rerun:

  • pnpm --filter smtp-server exec tsc --noEmit
  • pnpm exec prettier --check apps/smtp-server/src/server.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
apps/smtp-server/src/server.ts (1)

102-108: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Attachment payload now leaks sensitive content through error logs.

Because attachments are now embedded as base64 in emailObject, the existing raw log at Line 41 (emailDataText) will leak attachment bodies (and email PII) on API failures. Please sanitize before logging.

Suggested fix
 async function sendEmailToUseSend(emailData: any, apiKey: string) {
   try {
@@
-    const emailDataText = JSON.stringify(emailData);
+    const emailDataText = JSON.stringify(emailData);
+    const emailDataForLog = {
+      ...emailData,
+      to: emailData.to ? "[REDACTED]" : undefined,
+      from: emailData.from ? "[REDACTED]" : undefined,
+      replyTo: emailData.replyTo ? "[REDACTED]" : undefined,
+      attachments: Array.isArray(emailData.attachments)
+        ? emailData.attachments.map((a: any) => ({
+            filename: a.filename,
+            contentType: a.contentType,
+            size:
+              typeof a.content === "string"
+                ? Buffer.byteLength(a.content, "base64")
+                : undefined,
+          }))
+        : undefined,
+    };
@@
-        `\nemail data: ${emailDataText}`,
+        `\nemail data: ${JSON.stringify(emailDataForLog)}`,
       );

Based on learnings: enforce repository-wide PII redaction in logs and avoid ad-hoc exposure of email identifiers/content.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/smtp-server/src/server.ts` around lines 102 - 108, The code is embedding
full attachment bodies as base64 into emailObject (via parsed.attachments) which
causes emailDataText logs to leak PII and attachment content; fix by removing or
redacting attachment content before any logging: change the construction that
maps parsed.attachments so it only includes safe metadata (e.g., filename,
contentType, size) and omits or replaces content with a placeholder like
"<redacted_attachment>" and update any code that builds emailDataText to use
this sanitized version (reference parsed.attachments, emailObject, and
emailDataText) so no raw attachment bytes or sensitive fields are ever logged;
apply the same redaction pattern for other personal fields when assembling logs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/smtp-server/src/server.ts`:
- Around line 104-107: The attachment mapping in parsed.attachments currently
builds objects with filename and base64 content but omits the attachment MIME
type; update the mapper used where parsed.attachments.map(...) is defined so
each returned object also includes the original attachment.contentType (e.g.,
contentType: attachment.contentType) or a sensible fallback, preserving MIME
metadata for downstream MIME handling in the code paths that use this mapped
array.

---

Duplicate comments:
In `@apps/smtp-server/src/server.ts`:
- Around line 102-108: The code is embedding full attachment bodies as base64
into emailObject (via parsed.attachments) which causes emailDataText logs to
leak PII and attachment content; fix by removing or redacting attachment content
before any logging: change the construction that maps parsed.attachments so it
only includes safe metadata (e.g., filename, contentType, size) and omits or
replaces content with a placeholder like "<redacted_attachment>" and update any
code that builds emailDataText to use this sanitized version (reference
parsed.attachments, emailObject, and emailDataText) so no raw attachment bytes
or sensitive fields are ever logged; apply the same redaction pattern for other
personal fields when assembling logs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 073e351d-3242-4831-a684-acdd40fb05c1

📥 Commits

Reviewing files that changed from the base of the PR and between c3e6657 and e5ffda8.

📒 Files selected for processing (1)
  • apps/smtp-server/src/server.ts

Comment on lines +104 to +107
? parsed.attachments.map((attachment, index) => ({
filename: attachment.filename || `attachment-${index + 1}`,
content: attachment.content.toString("base64"),
}))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

contentType metadata is being dropped from forwarded attachments.

The mapped object includes filename and content, but not contentType. This misses the stated objective to preserve attachment metadata and may break downstream MIME handling.

Suggested fix
         attachments:
           parsed.attachments.length > 0
             ? parsed.attachments.map((attachment, index) => ({
                 filename: attachment.filename || `attachment-${index + 1}`,
+                contentType: attachment.contentType,
                 content: attachment.content.toString("base64"),
               }))
             : undefined,
📝 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.

Suggested change
? parsed.attachments.map((attachment, index) => ({
filename: attachment.filename || `attachment-${index + 1}`,
content: attachment.content.toString("base64"),
}))
? parsed.attachments.map((attachment, index) => ({
filename: attachment.filename || `attachment-${index + 1}`,
contentType: attachment.contentType,
content: attachment.content.toString("base64"),
}))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/smtp-server/src/server.ts` around lines 104 - 107, The attachment
mapping in parsed.attachments currently builds objects with filename and base64
content but omits the attachment MIME type; update the mapper used where
parsed.attachments.map(...) is defined so each returned object also includes the
original attachment.contentType (e.g., contentType: attachment.contentType) or a
sensible fallback, preserving MIME metadata for downstream MIME handling in the
code paths that use this mapped array.

@KMKoushik
KMKoushik merged commit d2dc18f into main May 18, 2026
5 checks passed
@KMKoushik
KMKoushik deleted the fix/smtp-forward-attachments branch May 18, 2026 06:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SMTP don't support sending attachments

1 participant