Archivos
`files->list`, `listAll`, `iterate`, `get`, `download`, `listLinks`, `listAllLinks`, `iterateLinks`, `createLink`, `revokeLink`, `revokeAllLinks`, `stats`, `upload`, `delete` y `deleteMany`.
Todos los métodos
use OpenEmail\Constants\FileKinds;use OpenEmail\Constants\FileSorts; $largest = $client->files->list(kind: FileKinds::PDF, sort: FileSorts::LARGEST, limit: 1); foreach ($largest as $file) { foreach ($client->files->listAllLinks($file['id']) as $link) { echo $link['url'], ' ', $link['downloads'], PHP_EOL; } file_put_contents(basename($file['filename']), $client->files->download($file['id']));} $stats = $client->files->stats();echo $stats['totals']['files'], ' ', $stats['uploaded']['bytes'], PHP_EOL;Cada archivo que guarda el buzón, adjuntos enviados y recibidos y los archivos subidos a él: la página Archivos de la aplicación. download devuelve los bytes como cadena, que file_put_contents guarda sin cambios. Los enlaces son los enlaces de descarga con los que salió un archivo, con cuántas veces se descargó cada uno, y stats es la pestaña Analíticas.
list, listAll e iterate admiten q:, kind: y sort:, y también direction: (inbound, outbound o uploaded), address:, los archivos de una dirección comparada sin distinguir mayúsculas y minúsculas, y since: y until:, un DateTimeInterface o una cadena ISO 8601. since: incluye su momento y until: se detiene antes. Un DateTimeInterface se envía en UTC, y una cadena con una fecha sin hora, como 2026-09-01, se lee como medianoche UTC.
q: busca en el nombre del archivo y en su tipo. kind: es image, pdf, audio, video o text, y sort: es newest, el valor por defecto, oldest, largest o name. OpenEmail\Constants\FileKinds, FileSorts y FileDirections nombran las opciones. list devuelve una OpenEmail\Result\Page, y listAll devuelve todos los archivos en un solo array. iterate devuelve un Generator que entrega un archivo cada vez y solo obtiene la página siguiente cuando el bucle la necesita.
Leer necesita files:read, y subir, eliminar y publicar o revocar enlaces necesitan files:write, que incluye files:read. Una clave creada antes de que los archivos tuvieran ámbitos propios recibió los correspondientes. Una clave sin el ámbito se rechaza con un 403 insufficient_scope, lanzado como una PermissionException cuyo isScopeMissing() es true. Una clave limitada a direcciones o dominios concretos solo ve los archivos que llegaron a ellos, así que no ve los archivos subidos para todo el espacio de trabajo.
download guarda el archivo entero en memoria, así que descarga los archivos muy grandes de uno en uno. Pasa el nombre por basename() antes de escribirlo, como hace el ejemplo, porque viene de quien envió el archivo.
Enlaces públicos
use OpenEmail\Constants\FileDirections; $uploads = $client->files->listAll(direction: FileDirections::UPLOADED, since: new \DateTimeImmutable('2026-09-01T00:00:00Z'));$file = $uploads[0] ?? null; if ($file !== null) { $link = $client->files->createLink($file['id'], domain: 'acme.com'); echo $link['url'], PHP_EOL; $client->files->revokeLink($file['id'], $link['id']);}createLink publica un archivo en un enlace de descarga público que se abre sin iniciar sesión, y devuelve el enlace. El enlace vive en el host de archivos de domain: cuando ese dominio tiene uno, como files.acme.com, si no en el host de archivos de la dirección a la que pertenece el archivo, y si no en la dirección de la API. Un archivo que llegó o salió en un mensaje se copia primero a un almacenamiento público, y un programa o un script se rechaza con un 422 file_unshareable, lanzado como una ValidationException.
Cada createLink crea un enlace nuevo, así que nunca se reintenta tras perder la respuesta: busca antes el enlace con listLinks. revokeLink detiene un enlace para siempre, también en el correo que ya salió, y lo devuelve con revokedAt establecido. Revocar uno dos veces es seguro, así que el cliente lo reintenta como una lectura. Un enlace desconocido es un 404, lanzado como una NotFoundException. revokeAllLinks detiene a la vez todos los enlaces de un archivo e informa de cuántos en revoked.
Subir y eliminar
$report = "month,sent\n2026-09,1200\n";$upload = $client->files->upload($report, filename: 'report.csv', contentType: 'text/csv'); if ($upload['deletable']) { $client->files->delete($upload['id']);} $logo = $client->files->upload(new \SplFileInfo('logo.svg'));echo $logo['filename'], ' ', $logo['mimeType'], PHP_EOL; $result = $client->files->deleteMany(['file_0c4e7a91d2b84f63a5e19b7d', 'file_6bb640f5b99e47deb758f1f5']); foreach ($result['kept'] as $kept) { echo $kept['filename'], ' ', $kept['reason'], PHP_EOL;}upload guarda los bytes tal cual, hasta 100 MB, con el nombre indicado en filename:, y devuelve el archivo. Un envío lo adjunta como ['fileId' => $upload['id']] en los attachments de emails->send. Pasa contentType:, o datos que lleven su propio tipo, y cualquier otra cosa se guarda como application/octet-stream. El tipo nunca se deduce de filename:, así que una cadena de bytes necesita contentType: para guardarse como otra cosa. Nunca se reintenta, porque un segundo intento guardaría una segunda copia.
Los datos son una cadena de bytes, un SplFileInfo, un stream de fopen(), o un stream o archivo subido PSR-7. Un SplFileInfo o un stream abierto sobre un archivo no necesitan ninguno de los dos argumentos: el nombre es el del propio archivo, y el tipo sale de su extensión cuando el paquete la conoce, como .pdf o .png. Un archivo subido en Laravel o Symfony, y un archivo subido PSR-7, traen el nombre y el tipo que envió el navegador. Una cadena no lleva nombre, así que una cadena sin filename: lanza una InvalidArgumentException antes de enviar nada.
Una subida puede durar 600 segundos, o el timeout del cliente si es mayor, y timeout: fija otro límite para una sola llamada. Un cliente construido con timeout: 0 espera lo que tarde la subida.
Solo se puede eliminar un archivo subido del que nada depende, y deletable y usage en cada archivo lo indican de antemano. delete rechaza cualquier otro archivo con un 409 file_in_use, lanzado como una ConflictException. deleteMany admite hasta 100 ids, elimina lo que puede e informa del resto en kept, cada uno con su motivo, y en missing. Ninguno de los dos se reintenta. Un 404 en un segundo delete tras una respuesta perdida significa que el primero funcionó, y un segundo deleteMany informa de esos archivos en missing.