Skip to the documentation
PHP

Files

`files->list`, `listAll`, `iterate`, `get`, `download`, `listLinks`, `listAllLinks`, `iterateLinks`, `createLink`, `revokeLink`, `revokeAllLinks`, `stats`, `upload`, `delete` and `deleteMany`.

Every method

files.php
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;

Every file the mailbox holds, attachments sent and received and the files uploaded to it: the Files page of the app. download returns the bytes as a string, which file_put_contents saves unchanged. The links are the download links a file went out as, with how often each was fetched, and stats is the Analytics tab.

list, listAll and iterate take q:, kind: and sort:, and also direction: (inbound, outbound or uploaded), address:, the files of one address compared without regard to case, and since: and until:, a DateTimeInterface or an ISO 8601 string. since: includes its moment and until: stops before it. A DateTimeInterface is sent in UTC, and a string holding a bare date such as 2026-09-01 is read as midnight UTC.

q: searches the file name and its type. kind: is image, pdf, audio, video or text, and sort: is newest, the default, oldest, largest or name. OpenEmail\Constants\FileKinds, FileSorts and FileDirections name the choices. list returns one OpenEmail\Result\Page, and listAll returns every file in one array. iterate returns a Generator that yields one file at a time and fetches the next page only when the loop needs it.

Reading needs files:read, and uploading, deleting and publishing or revoking links need files:write, which includes files:read. A key made before files had scopes of their own was given the matching ones. A key without the scope is refused with a 403 insufficient_scope, thrown as a PermissionException whose isScopeMissing() is true. A key limited to particular addresses or domains sees only the files that arrived at them, so it does not see files uploaded for the whole workspace.

download holds the whole file in memory, so fetch very large files one at a time. Pass the name through basename() before writing it, as the sample does, because it came from whoever sent the file.

Upload and delete

upload.php
$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 stores the bytes as they are, up to 100 MB, under the name in filename:, and returns the file. A send attaches it as ['fileId' => $upload['id']] in the attachments of emails->send. Pass contentType:, or data that carries its own type, and anything else is stored as application/octet-stream. The type is never guessed from filename:, so a string of bytes needs contentType: to be stored as anything else. It is never retried, because a second attempt would store a second copy.

The data is a string of bytes, an SplFileInfo, a stream from fopen(), or a PSR-7 stream or uploaded file. An SplFileInfo or a stream opened on a file needs neither argument: the name is the file’s own, and the type comes from its extension when the package knows it, such as .pdf or .png. A Laravel or Symfony upload, and a PSR-7 uploaded file, bring the name and type the browser sent. A string carries no name, so a string without filename: throws an InvalidArgumentException before anything is sent.

An upload may run for 600 seconds, or for the client’s timeout when that is longer, and timeout: sets another limit for one call. A client built with timeout: 0 waits as long as an upload takes.

Only an upload that nothing depends on can be deleted, and deletable and usage on each file say so ahead of time. delete refuses any other file with a 409 file_in_use, thrown as a ConflictException. deleteMany takes up to 100 ids, deletes what it can, and reports the rest in kept, each with its reason, and in missing. Neither is retried. A 404 on a second delete after a lost answer means the first one worked, and a second deleteMany reports those files in missing.