> ## Documentation Index
> Fetch the complete documentation index at: https://docs.talario.eu/llms.txt
> Use this file to discover all available pages before exploring further.

# Attachments and download links

> Download the original message and attachments, and get new links when they expired.

Talario never sends message bytes inside a webhook or an API response. Original messages and attachments are downloaded directly from the message store through **presigned links**.

## Properties of a link

* A link is valid for at most **1 hour** and never after the message's `expires_at`. Its expiry is in the `expires_at` next to it.
* A link can be used several times until it expires. It needs no `Authorization` header; do not send your API key to it.
* The link is checked when a download starts. A download that started before the expiry may finish afterwards.
* A link is a bearer credential for one file. Do not log it, store it or show it to users.
* Every webhook attempt and every API response contains newly signed links.

## Download and verify

Check the size and the SHA-256 of every file you download against the values from Talario before you rely on it.

```bash cURL theme={null}
curl -sS -o message.eml "$DOWNLOAD_URL"
sha256sum message.eml   # compare with download.sha256 or message.sha256
```

```php Laravel theme={null}
use Illuminate\Http\File;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;

$path = tempnam(sys_get_temp_dir(), 'talario');

try {
    Http::timeout(300)->sink($path)->get($url)->throw();

    if (filesize($path) !== $expectedSize || ! hash_equals($expectedSha256, hash_file('sha256', $path))) {
        throw new RuntimeException('The download does not match its checksum.');
    }

    Storage::putFileAs('inbound', new File($path), "{$messageId}.eml");
} finally {
    unlink($path);
}
```

| File | Size | SHA-256 |
| - | - | - |
| Original message | `download.size_bytes` or `message.size_bytes` | `download.sha256` or `message.sha256` |
| Attachment | `attachments[].size` | `attachments[].sha256` |

Treat every file as untrusted input. Do not trust the declared `content-type` or the file name; scan files before you pass them on.

## Get a new link

When a link expired, request a new one with an API key that may **Read received messages**. This works until the message's `expires_at`.

### Original message

```bash theme={null}
curl -X POST https://talario.eu/api/v1/receiving/messages/019a0520-0000-7000-8000-000000000002/download-url \
  -H "Authorization: Bearer $TALARIO_API_KEY"
```

```json theme={null}
{
  "url": "https://…",
  "expires_at": "2026-10-21T17:00:00Z"
}
```

### Attachment

Use the attachment `id` from the parsed content or the webhook:

```bash theme={null}
curl -X POST https://talario.eu/api/v1/receiving/messages/019a0520-0000-7000-8000-000000000002/attachments/part-2/download-url \
  -H "Authorization: Bearer $TALARIO_API_KEY"
```

The response has the same format. You can also call [`GET /content`](/receiving/parsed-content) again, which returns new links for all files.

### Laravel

```php theme={null}
use Illuminate\Support\Facades\Http;

$talario = Http::baseUrl('https://talario.eu/api/v1')
    ->withToken(config('services.talario.api_key'))
    ->acceptJson();

$original = $talario->post("receiving/messages/{$messageId}/download-url")->throw()->json();
$attachment = $talario->post("receiving/messages/{$messageId}/attachments/{$attachmentId}/download-url")->throw()->json();

// $original['url'], $original['expires_at']
```

### Errors

| Status | Code | Meaning |
| - | - | - |
| 404 | `not_found` | Unknown message, or the message has no attachment with this ID. |
| 409 | `message_not_stored` | The original is not in the message store (yet). |
| 409 | `content_not_ready`, `content_failed` | Attachments only: there is no parsed content (yet). |
| 410 | `message_expired` | The retention period ended. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.