---
title: "Files"
description: "`files.list`, `list_all`, `iterate`, `get`, `download`, `list_links`, `list_all_links`, `iterate_links`, `create_link`, `revoke_link`, `stats`, `upload`, `delete` and `delete_many`."
url: "https://openemail.uk/docs/ruby/files"
area: "Ruby"
category: "Mailbox"
---

# Files

`files.list`, `list_all`, `iterate`, `get`, `download`, `list_links`, `list_all_links`, `iterate_links`, `create_link`, `revoke_link`, `stats`, `upload`, `delete` and `delete_many`.

## Every method

**files.rb**

```
page = client.files.list(kind: "pdf", sort: "largest")
file = page.items.first

links = client.files.list_all_links(file[:id])
p links.map { |link| [link[:url], link[:downloads]] }

stats = client.files.stats
puts stats.dig(:totals, :files), stats.dig(:uploaded, :bytes)

File.binwrite(file[:filename], client.files.download(file[:id]))
```

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 binary String, which `File.binwrite` 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`, `list_all` 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 `Time`, a `DateTime` or an ISO 8601 String. `since:` includes its moment and `until:` stops before it. A Ruby Date is sent as a bare date, which this list reads 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::FILE_KINDS`, `OpenEmail::FILE_SORTS` and `OpenEmail::FILE_DIRECTIONS` name the choices. `list` returns one `OpenEmail::Page`, and `list_all` returns every file in one Array. `iterate` yields each file to a block and fetches the next page only when the loop needs it. Without a block it returns an Enumerator.

> 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`, and `scope_missing?` on the error 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.

## Public links

**links.rb**

```
uploads = client.files.list_all(direction: "uploaded", since: Time.utc(2026, 9, 1))
file = uploads.first

link = client.files.create_link(file[:id], domain: "acme.com")
puts link[:url]

client.files.revoke_link(file[:id], link[:id])
```

`create_link` publishes a file at a public download link that opens with no sign-in, and returns the link. The link lives on the files host of `domain:` when that domain has one, such as `files.acme.com`, else on the files host of the address the file belongs to, else on the API address. A file that came in or went out on a message is copied to public storage first, and a program or a script is refused with a 422 `file_unshareable`, raised as `OpenEmail::ValidationError`.

> Each `create_link` makes a new link, so it is never retried after a lost answer: look for the link with `list_links` first. `revoke_link` stops a link for good, including in mail that already went out, and returns it with `revokedAt` set. Revoking one twice is safe, so the gem retries it like a read. An unknown link is a 404, raised as `OpenEmail::NotFoundError`.

## Upload and delete

**upload.rb**

```
upload = client.files.upload(File.binread("report.pdf"), filename: "report.pdf", content_type: "application/pdf")
client.files.delete(upload[:id]) if upload[:deletable]

invoice = client.files.upload(Pathname("invoice.pdf"))
puts invoice[:filename], invoice[:mimeType]

result = client.files.delete_many(["file_0c4e7a91d2b84f63a5e19b7d", "file_6bb640f5b99e47deb758f1f5"])
result[:kept].each { |kept| puts "#{kept[:filename]} #{kept[:reason]}" }
```

`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 `content_type:`, or bytes that carry their own type, and anything else is stored as `application/octet-stream`. It is never retried, because a second attempt would store a second copy.

> The bytes are a binary String, an IO or a Pathname. A Pathname or a File needs neither keyword: the name is the file’s own, and the type comes from its extension when the gem knows it, such as `.pdf` or `.png`. A Rails upload brings its own `original_filename` and `content_type`. Plain bytes carry no name, so a String or a StringIO without `filename:` raises ArgumentError 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`, raised as `OpenEmail::ConflictError`. `delete_many` 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 `delete_many` reports those files in `missing`.
