payloadcms/payload · error · APIError

Attachment is missing both content and path

Error message

Attachment is missing both content and path

What it means

Thrown by the Resend email provider's attachment mapper when an attachment object has a filename but neither a `content` value nor a `path`. The mapper can only forward an attachment to the Resend API if it has bytes to send, so an attachment that is filename-only is rejected as malformed input (HTTP 400). This is a client-side validation gate before the Resend HTTP call is ever made.

Source

Thrown at packages/email-resend/src/index.ts:156

  }

  return [addresses.address]
}

function mapAttachments(
  attachments: SendEmailOptions['attachments'],
): ResendSendEmailOptions['attachments'] {
  if (!attachments) {
    return []
  }

  return attachments.map((attachment): Attachment => {
    if (!attachment.filename) {
      throw new APIError('Attachment is missing filename', 400)
    }

    if (!attachment.content && !attachment.path) {
      throw new APIError('Attachment is missing both content and path', 400)
    }

    // When both content and path are provided, content takes priority; path is ignored.
    if (attachment.path && !attachment.content) {
      const path = typeof attachment.path === 'string' ? attachment.path : attachment.path.href
      return {
        filename: attachment.filename,
        path,
      }
    }

    if (typeof attachment.content === 'string') {
      return {
        content: attachment.content,
        filename: attachment.filename,
      }
    }

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Supply `path` (a file path, URL string, or URL object) OR `content` (string or Buffer) on every attachment: `attachments: [{ filename: 'x.pdf', path: '/tmp/x.pdf' }]`.
  2. Filter out incomplete attachments before sending: `attachments.filter(a => a.filename && (a.content || a.path))`.
  3. If loading content asynchronously, await the file read so the variable is not undefined: `content: fs.readFileSync(p)`.

Example fix

// before
attachments: [{ filename: 'invoice.pdf' }]

// after (local file)
attachments: [{ filename: 'invoice.pdf', path: '/tmp/invoice.pdf' }]
// after (remote URL)
attachments: [{ filename: 'invoice.pdf', path: 'https://cdn.example.com/invoice.pdf' }]
// after (in-memory)
attachments: [{ filename: 'invoice.pdf', content: Buffer.from(raw) }]
Defensive patterns

Strategy: validation

Validate before calling

function isAttachmentComplete(a) {
  return Boolean(a && a.filename && (a.content != null || a.path != null && a.path !== ''))
}
const safe = attachments.filter(isAttachmentComplete)

Type guard

import type { SendEmailOptions } from 'payload'
function isCompleteAttachment(a: Partial<SendEmailOptions['attachments'][number]>): a is SendEmailOptions['attachments'][number] {
  return Boolean(a.filename && (a.content != null || (!!a.path && a.path !== '')))
}

Try / catch

try {
  await payload.sendEmail({ to, from, subject, attachments: safe })
} catch (e) {
  if (e instanceof APIError && e.message.includes('Attachment is missing both')) {
    // surface a friendly 'attachment source missing' error to the caller
  }
  throw e
}

Prevention

When it happens

Trigger: Calling sendEmail with `attachments: [{ filename: 'report.pdf' }]` (no content/path), or passing an attachment built from a partial object where the file source was conditionally omitted, e.g. `attachments: [{ filename, path: maybePath }]` when `maybePath` is undefined. Also when `attachment.path` is set to an empty string or null while `attachment.content` is also absent.

Common situations: Building attachments dynamically from uploaded files where the upload stream/path variable is undefined; copy-pasting an attachment example that only shows filename; migrating from another provider whose API treated content as optional; passing a URL string directly as the attachment instead of wrapping it as `{ filename, path: url }`.

Related errors


AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12). Data as JSON: /api/errors/4da17403f67e9448. Report an issue: GitHub.