payloadcms/payload · error · APIError
Attachment content must be a string or a buffer
Error message
Attachment content must be a string or a buffer
What it means
Thrown by the Resend attachment mapper after it has confirmed the attachment uses the `content` branch (not `path`) but the `content` value is neither a `string` nor a `Buffer`. The mapper checks `typeof === 'string'` first, then `instanceof Buffer`, and falls through to this error for any other type (number, object, array, typed array that is not Buffer, etc.). It is a 400 APIError surfaced from the Payload→Resend transport layer.
Source
Thrown at packages/email-resend/src/index.ts:182
path,
}
}
if (typeof attachment.content === 'string') {
return {
content: attachment.content,
filename: attachment.filename,
}
}
if (attachment.content instanceof Buffer) {
return {
content: attachment.content,
filename: attachment.filename,
}
}
throw new APIError('Attachment content must be a string or a buffer', 400)
})
}
function mapHeaders(headers: SendEmailOptions['headers']): Record<string, string> | undefined {
if (!headers) {
return undefined
}
// Array-of-objects form: [{ key: string; value: string }, ...]
if (Array.isArray(headers)) {
return headers.reduce<Record<string, string>>((acc, { key, value }) => {
acc[key] = value
return acc
}, {})
}
// Object form: { [key: string]: string | string[] | { prepared: boolean; value: string } }
return Object.entries(headers).reduce<Record<string, string>>((acc, [key, value]) => {View on GitHub (pinned to 00c58b35c0)
Solutions
- Convert the value to a Buffer before attaching: `content: Buffer.from(uint8array)` or `Buffer.from(arrayBuffer)`.
- If content is JSON or text, pass it as a string: `content: JSON.stringify(obj)`.
- Type the attachment content field strictly as `string | Buffer` in your own call site and enforce it before calling sendEmail.
Example fix
// before
attachments: [{ filename: 'sig.bin', content: crypto.getRandomValues(new Uint8Array(16)) }]
// after
attachments: [{ filename: 'sig.bin', content: Buffer.from(crypto.getRandomValues(new Uint8Array(16))) }] Defensive patterns
Strategy: type-guard
Validate before calling
function toBufferContent(content) {
if (typeof content === 'string') return content
if (content instanceof Buffer) return content
if (ArrayBuffer.isView(content)) return Buffer.from(content.buffer, content.byteOffset, content.byteLength)
if (content instanceof ArrayBuffer) return Buffer.from(content)
throw new Error('attachment content must be string or Buffer')
} Type guard
function isStringOrBuffer(v: unknown): v is string | Buffer {
return typeof v === 'string' || Buffer.isBuffer(v)
} Try / catch
try {
await payload.sendEmail({ ..., attachments: [{ filename, content: toBufferContent(raw) }] })
} catch (e) {
if (e instanceof APIError && /content must be a string or a buffer/.test(e.message)) {
// normalize content before retry
}
throw e
} Prevention
- Always coerce typed arrays/ArrayBuffers with `Buffer.from(...)` before assigning to attachment.content.
- Keep a strict `string | Buffer` type on your own attachment builder.
- Unit-test the attachment builder with a Uint8Array input.
When it happens
Trigger: Setting `attachment.content` to a non-string/non-Buffer value such as a number, plain object, array, or a Uint8Array/ArrayBuffer (which are NOT instances of Buffer). For example `attachments: [{ filename: 'd.bin', content: new Uint8Array([1,2,3]) }]` triggers it because `Uint8Array` is not `Buffer`.
Common situations: Browser-side code or Web Crypto / fetch APIs that return `Uint8Array`/`ArrayBuffer` instead of Node `Buffer`; passing a parsed JSON number/object directly as attachment content; assuming all typed arrays are Buffers; receiving content from a stream and not wrapping it with `Buffer.from()`.
Related errors
- Attachment is missing filename
- Attachment is missing both content and path
- Error sending email: ${statusCode}
- Username or email is required
- Email is required.
AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12).
Data as JSON: /api/errors/3ab52bdb228059cc.
Report an issue: GitHub.