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

# Create Attachment Upload

> Generate a presigned URL for uploading an attachment

Create a presigned URL for uploading an email attachment. After uploading the file to this URL, you must confirm the upload before using the attachment.

## Upload Flow

Attachments use a three-step process:

1. **Create Upload** - Get a presigned URL (this endpoint)
2. **Upload File** - PUT the file directly to the presigned URL
3. **Confirm Upload** - Notify MailBreeze the upload is complete
4. **Use Attachment** - Include attachment ID when sending emails

## Request Body

<ParamField body="filename" type="string" required>
  Original filename (e.g., `report.pdf`). Used for display in email clients.
</ParamField>

<ParamField body="contentType" type="string" required>
  MIME type of the file (e.g., `application/pdf`, `image/png`).
</ParamField>

<ParamField body="size" type="integer" required>
  File size in bytes. Maximum 40MB for regular attachments, 5MB for inline images.
</ParamField>

<ParamField body="inline" type="boolean" default="false">
  Set to `true` for embedded images in HTML emails (returns `contentId`).
</ParamField>

## Examples

<CodeGroup>
  ```typescript JavaScript theme={null}
  import { MailBreeze } from "mailbreeze";

  const mailbreeze = new MailBreeze({ apiKey: "sk_live_xxx" });

  // Step 1: Create upload URL
  const { attachmentId, uploadUrl, uploadToken, expiresAt } =
    await mailbreeze.attachments.createUpload({
      fileName: "report.pdf",
      contentType: "application/pdf",
      fileSize: 1024000, // ~1MB
    });

  console.log(`Upload URL expires at: ${expiresAt}`);

  // Step 2: Upload file directly to the presigned URL
  const fileBuffer = await fs.readFile("./report.pdf");
  await fetch(uploadUrl, {
    method: "PUT",
    body: fileBuffer,
    headers: {
      "Content-Type": "application/pdf",
      "Content-Length": fileBuffer.length.toString(),
    },
  });

  // Step 3: Confirm the upload
  const attachment = await mailbreeze.attachments.confirm({ uploadToken });
  console.log(attachment.status); // "uploaded"

  // Step 4: Use in email
  await mailbreeze.emails.send({
    from: "hello@yourdomain.com",
    to: "user@example.com",
    subject: "Your report is ready",
    html: "<p>Please find your report attached.</p>",
    attachmentIds: [attachmentId],
  });
  ```

  ```python Python theme={null}
  import aiohttp
  from mailbreeze import MailBreeze

  client = MailBreeze(api_key="sk_live_xxx")

  # Step 1: Create upload URL
  result = await client.attachments.create_upload(
      file_name="report.pdf",
      content_type="application/pdf",
      file_size=1024000,
  )

  print(f"Upload URL expires at: {result.expires_at}")

  # Step 2: Upload file directly
  with open("./report.pdf", "rb") as f:
      file_data = f.read()

  async with aiohttp.ClientSession() as session:
      await session.put(
          result.upload_url,
          data=file_data,
          headers={"Content-Type": "application/pdf"},
      )

  # Step 3: Confirm the upload
  attachment = await client.attachments.confirm(upload_token=result.upload_token)
  print(attachment.status)  # "uploaded"

  # Step 4: Use in email
  await client.emails.send(
      from_="hello@yourdomain.com",
      to="user@example.com",
      subject="Your report is ready",
      html="<p>Please find your report attached.</p>",
      attachment_ids=[result.attachment_id],
  )
  ```

  ```go Go theme={null}
  client := mailbreeze.NewClient("sk_live_xxx")

  // Step 1: Create upload URL
  result, err := client.Attachments.CreateUpload(ctx, &mailbreeze.CreateAttachmentUploadParams{
      FileName:    "report.pdf",
      ContentType: "application/pdf",
      FileSize:    1024000,
  })
  if err != nil {
      log.Fatal(err)
  }

  fmt.Printf("Upload URL expires at: %s\n", result.ExpiresAt)

  // Step 2: Upload file directly
  fileData, _ := os.ReadFile("./report.pdf")
  req, _ := http.NewRequest("PUT", result.UploadURL, bytes.NewReader(fileData))
  req.Header.Set("Content-Type", "application/pdf")
  http.DefaultClient.Do(req)

  // Step 3: Confirm the upload
  attachment, err := client.Attachments.Confirm(ctx, &mailbreeze.ConfirmAttachmentParams{
      UploadToken: result.UploadToken,
  })

  // Step 4: Use in email
  client.Emails.Send(ctx, &mailbreeze.SendEmailParams{
      From:          "hello@yourdomain.com",
      To:            []string{"user@example.com"},
      Subject:       "Your report is ready",
      HTML:          "<p>Please find your report attached.</p>",
      AttachmentIds: []string{result.AttachmentID},
  })
  ```

  ```php PHP theme={null}
  $mailbreeze = new MailBreeze('sk_live_xxx');

  // Step 1: Create upload URL
  $response = $mailbreeze->attachments->createUpload([
      'fileName' => 'report.pdf',
      'contentType' => 'application/pdf',
      'fileSize' => 1024000,
  ]);
  $result = $response['data'];

  echo "Upload URL expires at: " . $result['expiresAt'] . "\n";

  // Step 2: Upload file directly
  $fileData = file_get_contents('./report.pdf');
  $ch = curl_init($result['uploadUrl']);
  curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
  curl_setopt($ch, CURLOPT_POSTFIELDS, $fileData);
  curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/pdf']);
  curl_exec($ch);
  curl_close($ch);

  // Step 3: Confirm the upload
  $attachment = $mailbreeze->attachments->confirm($result['attachmentId']);

  // Step 4: Use in email
  $mailbreeze->emails->send([
      'from' => 'hello@yourdomain.com',
      'to' => 'user@example.com',
      'subject' => 'Your report is ready',
      'html' => '<p>Please find your report attached.</p>',
      'attachmentIds' => [$result['attachmentId']],
  ]);
  ```

  ```rust Rust theme={null}
  let client = Client::new("sk_live_xxx");

  // Step 1: Create upload URL
  let result = client.attachments().create_upload(CreateAttachmentUploadParams {
      file_name: "report.pdf".to_string(),
      content_type: "application/pdf".to_string(),
      file_size: 1024000,
  }).await?;

  println!("Upload URL expires at: {}", result.expires_at);

  // Step 2: Upload file directly
  let file_data = std::fs::read("./report.pdf")?;
  reqwest::Client::new()
      .put(&result.upload_url)
      .header("Content-Type", "application/pdf")
      .body(file_data)
      .send()
      .await?;

  // Step 3: Confirm the upload
  let attachment = client.attachments().confirm(ConfirmAttachmentParams {
      upload_token: result.upload_token,
  }).await?;

  // Step 4: Use in email
  client.emails().send(SendEmailParams {
      from: "hello@yourdomain.com".to_string(),
      to: vec!["user@example.com".to_string()],
      subject: Some("Your report is ready".to_string()),
      html: Some("<p>Please find your report attached.</p>".to_string()),
      attachment_ids: Some(vec![result.attachment_id]),
      ..Default::default()
  }).await?;
  ```

  ```bash cURL theme={null}
  # Step 1: Create presigned upload URL
  curl -X POST https://api.mailbreeze.com/api/v1/attachments/presigned-url \
    -H "Authorization: Bearer sk_live_xxx" \
    -H "Content-Type: application/json" \
    -d '{
      "filename": "report.pdf",
      "contentType": "application/pdf",
      "size": 1024000
    }'

  # Step 2: Upload file to the presigned URL
  curl -X PUT "https://storage.mailbreeze.com/..." \
    -H "Content-Type: application/pdf" \
    --data-binary @report.pdf

  # Step 3: (Optional) Confirm upload
  curl -X POST https://api.mailbreeze.com/api/v1/attachments/att_abc123/confirm \
    -H "Authorization: Bearer sk_live_xxx"
  ```
</CodeGroup>

## Response

<ResponseField name="attachmentId" type="string">
  Unique attachment ID to use when sending emails.
</ResponseField>

<ResponseField name="uploadUrl" type="string">
  Presigned URL for uploading the file. PUT your file here.
</ResponseField>

<ResponseField name="expiresAt" type="string">
  ISO 8601 timestamp when the upload URL expires (typically 1 hour).
</ResponseField>

<ResponseField name="contentId" type="string">
  For inline attachments only. Use this in HTML: `<img src="cid:contentId">`.
</ResponseField>

```json Example Response theme={null}
{
  "success": true,
  "data": {
    "attachmentId": "att_abc123",
    "uploadUrl": "https://storage.mailbreeze.com/uploads/att_abc123?signature=...",
    "expiresAt": "2024-01-15T11:30:00Z"
  },
  "meta": {
    "timestamp": "2024-01-15T10:30:00.000Z",
    "requestId": "req_abc123",
    "path": "/api/v1/attachments/presigned-url"
  }
}
```

```json Example Response (Inline Image) theme={null}
{
  "success": true,
  "data": {
    "attachmentId": "att_abc123",
    "uploadUrl": "https://storage.mailbreeze.com/uploads/att_abc123?signature=...",
    "expiresAt": "2024-01-15T11:30:00Z",
    "contentId": "image001"
  },
  "meta": {
    "timestamp": "2024-01-15T10:30:00.000Z",
    "requestId": "req_abc123",
    "path": "/api/v1/attachments/presigned-url"
  }
}
```

## Limits

| Limit                               | Value                     |
| ----------------------------------- | ------------------------- |
| Max file size                       | 10MB                      |
| Upload URL validity                 | 1 hour                    |
| Attachment validity                 | 7 days after confirmation |
| Max attachments per email           | 10                        |
| Max total attachment size per email | 25MB                      |

## Errors

| Code                   | HTTP Status | Description                   |
| ---------------------- | ----------- | ----------------------------- |
| `ATTACHMENT_TOO_LARGE` | 400         | File size exceeds 10MB limit  |
| `INVALID_CONTENT_TYPE` | 400         | Unsupported MIME type         |
| `VALIDATION_ERROR`     | 400         | Missing or invalid parameters |
