# Send Messages via S3 Delivery

The S3 Delivery flow allows you to send messages, such as invoices, letters, or payslips, to one or more recipients through the Digital Post Platform (DPP). Each message is described by an envelope, a JSON file that specifies the recipient(s), sender, message title, and references to the content files to be delivered.

## Prerequisites

- A registered sender organization with at least one configured sender
- API credentials (OAuth 2.0 client)
- SendMessage permission granted for the sender organization
- An Amazon S3 bucket accessible by the DPP


## How It Works

- Create envelopes: For each message you want to send, create a JSON envelope file containing the recipient details, sender information, and references to the content files (for example, PDFs or documents).
- Upload to S3: Upload the envelopes and their referenced content files to an Amazon S3 bucket that the DPP can access using credentials you provide.
- Initiate delivery: Call the Delivery API with the S3 bucket connection details. The DPP reads the envelopes, validates them, retrieves the referenced files, and delivers them to the intended recipients.
- Monitor status: Use the delivery ID returned by the API to poll for the delivery status and review any processing errors.


## Batch size Recommendations for Large Recipient Volumes

> **Tip:** If you need to send a message to a large number of recipients (for example, 100,000 users), group your envelopes into batches of around 1,000 envelopes per delivery instead of creating a separate delivery for every envelope. Uploading and processing envelopes in batches of ~1,000 is far more efficient than one delivery per envelope and significantly reduces overall delivery time.


## Delivery Lifecycle

```text
Create envelopes -> Upload to S3 -> POST /delivery/v1/deliveries -> DPP processes envelopes
                                         v
                                   DeliveryId returned
                                         v
                              GET /delivery/v1/deliveries/{deliveryId}
                                         v
                              Status: Processing -> Completed
```

## Validation Rules

| Field | Required | Constraint |
|  --- | --- | --- |
| `messageId` | Required | 1-71 characters. Allowed characters: `a-zA-Z0-9/\._-`. Must be unique within the sender organization. |
| `title` | Required | Maximum 101 characters. |
| `sender` | Required | Must include valid `senderId` and `organizationId`. |
| `sender.senderId` | Required | Must be a valid sender ID within the organization. |
| `sender.organizationId` | Required | Must be a valid organization ID. |
| `recipients` | Required | Between 1 and 10 recipients. No duplicate identity values. |
| `recipients[].identity` | Required | Exactly one identity key-value pair per recipient. Key max 50 chars, matching `a-zA-Z0-9/\._-`. Value must not be empty. |
| `body` | Required | Must include `name`, `filename`, and `mimeType`. |
| `body.name` | Required | Maximum 101 characters. Must match the S3 object key (relative to `s3Prefix`). |
| `body.filename` | Required | The filename shown to the recipient when exporting. |
| `body.mimeType` | Required | Must be a valid content type (for example, `application/pdf`, `text/plain`). |
| `body.title` | Optional | Maximum 101 characters. |
| `attachments` | Optional | Array of attachment file references. |
| `attachments[].name` | Required (if attachment present) | Maximum 101 characters. Must match the S3 object key (relative to `s3Prefix`). |
| `attachments[].filename` | Required (if attachment present) | The filename shown to the recipient when exporting. |
| `attachments[].mimeType` | Required (if attachment present) | Must be a valid content type. |
| `attachments[].title` | Optional | Maximum 101 characters. |


## S3 Upload Rules

| Rule | Required | Constraint |
|  --- | --- | --- |
| Envelope content type | Required | Must be `application/vnd+e-boks.emf+json`. |
| Envelope S3 object key | Required | Must follow `{s3Prefix}{s3EnvelopePrefix}{messageId}`. |
| Content file S3 key | Required | Must match `{s3Prefix}{body.name}` or `{s3Prefix}{attachments[].name}`. |
| Content file content type | Required | Must conform to [envelope-schema.md](/apis/schemas/envelope-schema). |


## Step 1: Create an Envelope for an Invoice

We want to send an invoice to two people. The invoice is the file `invoice-880942299.pdf`.

- Sender: We use `senderId` 3 from organization with ID 6. Refer to the Sender API for details on creating or listing senders.
- Recipient: We specify one recipient using an identity type and value. See Recipient Identification for supported identity types.
- Message body: We reference the invoice PDF and choose the filename the recipient sees when exporting.
- Message ID: We choose `invoice-880942299` and it must be unique within the sender's organization.
- Title: The message subject shown to recipients.


Save the following as `invoice-880942299.json`:

```json
{
  "messageId": "invoice-880942299",
  "title": "Invoice",
  "sender": {
    "senderId": 3,
    "organizationId": 6
  },
  "recipients": [
    {
      "identity": {
        "Username": "invoiced_recipient-1"
      }
    },
    {
      "identity": {
        "Username": "invoiced_recipient-2"
      }
    }
  ],
  "body": {
    "name": "invoice-880942299.pdf",
    "filename": "invoice.pdf",
    "mimeType": "application/pdf"
  }
}
```

## Step 2: Create an Envelope for Pay Slips

We want to send a pay slip to an employee. The message body is a plain-text note informing the recipient that their pay slip is attached. The actual pay slip is provided as a PDF attachment: `payslip-may-2026.pdf`.

- Sender: We use `senderId` 3 from organization with ID 6.
- Recipient: We specify one recipient, uniquely identified by an identity type and value.
- Message body: A plain-text file (`payslip-notice-may-2026.txt`) containing a short message to the recipient.
- Attachment: The pay slip PDF (`payslip-may-2026.pdf`) available for the recipient to download.
- Message ID: We choose `payslip-may-2026-emp-74219`.
- Title: The message subject shown to recipient.


Save the following as `payslip-may-2026-emp-74219.json`:

```json
{
  "messageId": "payslip-may-2026-emp-74219",
  "title": "Payslip for May 2026",
  "sender": {
    "senderId": 3,
    "organizationId": 6
  },
  "recipients": [
    {
      "identity": {
        "Username": "payslip_recipient-1"
      }
    }
  ],
  "body": {
    "name": "payslip-notice-may-2026.txt",
    "filename": "payslip-notice.txt",
    "mimeType": "text/plain"
  },
  "attachments": [
    {
      "name": "payslip-may-2026.pdf",
      "filename": "payslip-may-2026.pdf",
      "mimeType": "application/pdf"
    }
  ]
}
```

Constraints:

- Each recipient must have a unique identity value within the envelope.
- Both the body file and attachment must use a supported file type.


## Step 3: Prepare an S3 Bucket for Delivery of Messages

> **Note:** The S3 bucket and IAM credentials (access key and secret key) are provisioned by e-boks via Terraform as part of the customer onboarding process. You do not need to create a bucket or IAM user yourself. The bucket name, region, and access credentials will be shared with you during the onboarding handover.


Upload the content files and envelopes to the S3 bucket provided to you by e-boks.

### Bucket Structure

The DPP locates files using two prefixes:

| Concept | Description | Example |
|  --- | --- | --- |
| Key prefix (`s3Prefix`) | A common prefix for all objects in this delivery. The DPP resolves content file references relative to this prefix. | `delivery1/` |
| Envelope prefix (`s3EnvelopePrefix`) | A prefix for envelope objects, relative to the key prefix. The DPP lists all objects under `{s3Prefix}{s3EnvelopePrefix}` to discover envelopes. (Preferred `envelopes/`) | `envelopes/` |


Envelope key convention:

Each envelope object key must be `{s3Prefix}{s3EnvelopePrefix}{messageId}`, where `messageId` matches the `messageId` field inside the envelope JSON.

For the envelopes created in Steps 1 and 2, the resulting bucket structure is:

```text
my-bucket/
└── delivery1/                                  <- s3Prefix
    ├── invoice-880942299.pdf                   <- body file for invoice envelope
    ├── payslip-notice-may-2026.txt             <- body file for payslip envelope
    ├── payslip-may-2026.pdf                    <- attachment for payslip envelope
    └── envelopes/                              <- s3EnvelopePrefix (relative to s3Prefix)
        ├── invoice-880942299                   <- key tail = messageId, no extension
        └── payslip-may-2026-emp-74219          <- key tail = messageId, no extension
```

### Upload Content Files

Upload the files referenced by the envelope `body.name` and `attachments[].name` fields. Each file must be uploaded to `{s3Prefix}{name}` in the bucket, with the correct content type:

```bash
S3_PREFIX=delivery1/
# Invoice PDF (referenced by invoice envelope body.name)
aws s3 cp invoice-880942299.pdf s3://${BUCKET_NAME}/${S3_PREFIX}invoice-880942299.pdf --content-type application/pdf
# Payslip notice text (referenced by payslip envelope body.name)
aws s3 cp payslip-notice-may-2026.txt s3://${BUCKET_NAME}/${S3_PREFIX}payslip-notice-may-2026.txt --content-type text/plain
# Payslip PDF (referenced by payslip envelope attachments[0].name)
aws s3 cp payslip-may-2026.pdf s3://${BUCKET_NAME}/${S3_PREFIX}payslip-may-2026.pdf --content-type application/pdf
```

### Upload Envelopes

Upload each envelope JSON file. The object key must follow the convention `{s3Prefix}{s3EnvelopePrefix}{messageId}` and the content-type must be `application/vnd+e-boks.emf+json`:

```bash
ENVELOPE_PREFIX=envelopes/
# Invoice envelope (messageId = "invoice-880942299")
aws s3 cp invoice-880942299.json s3://${BUCKET_NAME}/${S3_PREFIX}${ENVELOPE_PREFIX}invoice-880942299 --content-type application/vnd+e-boks.emf+json
# Payslip envelope (messageId = "payslip-may-2026-emp-74219")
aws s3 cp payslip-may-2026-emp-74219.json s3://${BUCKET_NAME}/${S3_PREFIX}${ENVELOPE_PREFIX}payslip-may-2026-emp-74219 --content-type application/vnd+e-boks.emf+json
```

### File Encryption (Optional)

If S3 server-side encryption is not sufficient, each file can be individually encrypted using an AES-256 key before upload:

1. Generate a random AES-256 key.
2. Encrypt each content file with this key before uploading.
3. Base64-encode the key.
4. Include the base64-encoded key in the `encryptionKey` field when creating a delivery in Step 4.


See Encryption during delivery for details.

### Verify Your Bucket (Optional)

```bash
# List all objects
aws s3 ls s3://${BUCKET_NAME}/${S3_PREFIX} --recursive
# Verify an envelope's content-type
aws s3api head-object --bucket $BUCKET_NAME --key ${S3_PREFIX}${ENVELOPE_PREFIX}invoice-880942299 --query ContentType --output text
# Expected: application/vnd+e-boks.emf+json
```

## Step 4: Create a Delivery

Use the Delivery API to initiate the delivery. The platform will read the envelopes and content files from your S3 bucket and deliver them to the specified recipients.

Only create a delivery after all content has been uploaded successfully.

### Validation Rules

| Field | Constraint |
|  --- | --- |
| `s3ServiceUrl` | Required, max 200 chars |
| `s3BucketName` | Required, max 50 chars |
| `s3AccessKeyId` | Required, max 50 chars |
| `s3SecretKey` | Required, max 50 chars |
| `s3Prefix` | Required, max 200 chars |
| `s3EnvelopePrefix` | Required, max 100 chars |
| `s3UrlStyle` | Required, must be `PathStyle` or `VirtualHostedStyle` |
| `encryptionKey` | Optional, max 256 chars |
| `name` | Optional |
| `senderOrganizationId` | Optional (resolved from token if omitted) |


Request:

Requires: Valid authentication token with `SendMessage` permission

```http
POST {{server}}/delivery/v1/deliveries
Content-Type: application/json
Authorization: Bearer <access_token>
```

Request body:

```json
{
    "name": "June 2025 payslips and invoices",
    "senderOrganizationId": 6,
    "s3ServiceUrl": "https://s3.eu-central-1.amazonaws.com",
    "s3BucketName": "my-bucket",
    "s3AccessKeyId": "AKIAIOSFODNN7EXAMPLE",
    "s3SecretKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
    "s3Prefix": "delivery1/",
    "s3EnvelopePrefix": "envelopes/",
    "s3UrlStyle": "PathStyle",
    "encryptionKey": "<base64-encoded-key>"
}
```

Success response: `200 OK` - The `DeliveryId` is a unique identifier (GUID) for this delivery. Use it in Step 5 to check the delivery status.

```json
{
  "DeliveryId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
```

## Step 5: Check Delivery Report

Query the status and outcome of your delivery using the `DeliveryId` returned in Step 4.

### Query Parameters

The delivery report endpoint supports pagination for log entries:

| Parameter | Type | Default | Constraints | Description |
|  --- | --- | --- | --- | --- |
| `page` | int | 0 | >= 0 | Zero-based page index |
| `pageSize` | int | 2500 | 1–2500 (configurable) | Number of log entries per page |


> **Note:** The `pageSize` upper limit is configurable and currently set to 2500.


### Response Headers

The response includes pagination metadata in the following headers:

| Header | Description |
|  --- | --- |
| `Pagination-Total` | Total number of log entries |
| `Pagination-Page` | Current page number |
| `Pagination-PageSize` | Items per page |


### Examples

#### Basic request (first page, default size)

Requires: Valid authentication token with `SendMessage` or `ViewSendMessageHistory` permission

Replace `{{deliveryId}}` with the `DeliveryId` value from the Step 4 response.

```http
GET {{server}}/delivery/v1/deliveries/{{deliveryId}}
Authorization: Bearer <access_token>
```

#### Explicit pagination

```http
GET {{server}}/delivery/v1/deliveries/{{deliveryId}}?page=0&pageSize=100
Authorization: Bearer <access_token>
```

#### Subsequent pages

```http
GET {{server}}/delivery/v1/deliveries/{{deliveryId}}?page=1&pageSize=100
Authorization: Bearer <access_token>
```

#### Iterating through all pages

To retrieve all log entries, increment the `page` parameter until you have fetched all items:

1. Make an initial request (e.g., `?page=0&pageSize=100`).
2. Read the `Pagination-Total` response header to determine the total number of log entries.
3. Calculate the total pages: `ceiling(Pagination-Total / pageSize)`.
4. Request subsequent pages (`page=1`, `page=2`, ...) until all entries are retrieved.


### Success response: `200 OK`

Response headers:

```text
Pagination-Total: 150
Pagination-Page: 0
Pagination-PageSize: 100
```

Response body:

```json
{
  "report": {
    "name": "June 2025 payslips and invoices",
    "createdDate": "2025-06-15T10:30:00Z",
    "status": "Completed",
    "deliveredEnvelopes": 1,
    "totalEnvelopes": 2,
    "logs": [
      {
        "envelopeId": "payslip-may-2026-emp-74219",
        "delivered": false,
     "level": "Error",
        "key": "InvalidEnvelope",
        "description": "Attached envelope format is incorrect"
      }
    ]
  }
}
```

### Error response: `400 Bad Request`

Returned when `page` is less than 0 or `pageSize` is outside the valid range (1–2500):

```json
{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "instance": "urn:trace:00-abc123def456-789ghi-01",
  "errors": {
    "page": ["'page' must be greater than or equal to 0."],
    "pageSize": ["'pageSize' must be between 1 and 2500."]
  },
  "traceId": "00-abc123def456-789ghi-01"
}
```

## Recommended Validation Checklist

Before creating a delivery, verify:

- All envelope files exist
- All referenced files exist
- Paths match envelope references
- Prefix configuration is correct
- File sizes are within limits
- Optional encryption is configured correctly


Completing this checklist reduces delivery failures.

## What Happens After Submission

When an S3 delivery is submitted, the platform:

1. Lists envelope objects under `{s3Prefix}{s3EnvelopePrefix}`.
2. Retrieves envelope files and referenced content files from the configured S3 bucket.
3. Processes envelopes in batches as files are fetched.
4. Validates envelope structure and metadata.
5. Verifies that all referenced files exist and match the envelope references.
6. Scans content files.
7. Decrypts content when `encryptionKey` is provided.
8. Resolves recipient mailboxes.
9. Applies sender and feature rules.
10. Publishes messages atomically at envelope level (all recipients receive the message, or none do).


## Next Steps

- [Verify and Troubleshoot](/getting-started/verify-delivery)