---
title: "How to Import a Suppression List"
description: "If you're migrating from another ESP or need to seed your suppression list with addresses that should never receive email, AhaSend's Suppressions API lets you do that programmatically. This guide shows you how to bulk-import a suppression list using cURL, Python, and the Go SDK."
url: https://ahasend.com/blog/how-import-suppression-list-ahasend
markdown_url: https://ahasend.com/blog/how-import-suppression-list-ahasend.md
lang: en
type: blog_post
published: 2026-05-05
updated: 2026-08-28
author: "Mark Kraakman"
category: "Guides"
image: https://cms.ahasend.com/sites/default/files/2026-05/importing-a-existing-supression-list.png
---

# How to Import a Suppression List

_By Mark Kraakman, May 5, 2026, in Guides_

![How to Import a Suppression List](https://cms.ahasend.com/sites/default/files/2026-05/importing-a-existing-supression-list.png)

If you're migrating from another ESP or need to seed your suppression list with addresses that should never receive email, AhaSend's [Suppressions API](https://ahasend.com/docs/api-reference/suppressions/create-suppression) lets you do that programmatically. This guide shows you how to bulk-import a suppression list using cURL, Python, and the Go SDK.

## What are Suppressions?

A suppression list is your record of email addresses you should not send to - hard bounces, spam complaints, manual unsubscribes, or addresses you know are invalid. Sending to suppressed addresses hurts your sender reputation and, for hard bounces and complaints, may violate your terms of service with your ESP.

When migrating from another provider, importing your existing suppression list before sending your first email is one of the most important steps you can take. If you skip it, you'll repeat the bounces and complaints that built that list in the first place.

## Exporting your suppression list from another provider

Before you can import into AhaSend, you need to get your existing suppression list out of your current ESP.

- **SendGrid**: Use the [Retrieve all suppressions](https://www.twilio.com/docs/sendgrid/api-reference/suppressions-suppressions/retrieve-all-suppressions) endpoint to export all suppressed addresses via the API.
- **Postmark**: Go to your Message Stream's Suppressions tab and click Export, or use the [Suppressions API](https://postmarkapp.com/support/article/881-can-i-export-a-list-of-all-bounces) to pull a dump programmatically.

Once you have your list of addresses, save them to a plain text file with one email per line and follow the steps below.

## The API

Each suppression is created individually via a `POST` request. For bulk imports you'll loop over your list and [call the endpoint](https://ahasend.com/docs/api-reference/suppressions/create-suppression) once per address.

```plaintext
POST /v2/accounts/{account_id}/suppressions
```

**Required fields:**

- `email` - the address to suppress (must be a valid email address)
- `expires_at` - when the suppression expires (RFC3339 format)

**Optional fields:**

- `domain` - restrict the suppression to a specific sending domain. If omitted, the suppression applies to all domains on the account.
- `reason` - a note on why the address was suppressed (max 255 characters)

If you're importing permanent suppressions (hard bounces, spam complaints), set `expires_at` far in the future - for example, 10 years from now. AhaSend requires an expiry date, but nothing stops you from setting it well beyond your product's expected lifetime.

## Importing with cURL

For small lists or one-off imports, cURL is the quickest option:

```bash
curl -X POST https://api.ahasend.com/v2/accounts/YOUR_ACCOUNT_ID/suppressions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "bounced@example.com",
    "expires_at": "2035-01-01T00:00:00Z",
    "reason": "Hard bounce"
  }'
```

For a larger list, use a plain text file with one email address per line (no headers, no commas):

```plaintext
bounced@example.com
complaint@example.com
invalid@example.com
```

```bash
#!/bin/bash
API_KEY="YOUR_API_KEY"
ACCOUNT_ID="YOUR_ACCOUNT_ID"
EXPIRES_AT="2035-01-01T00:00:00Z"

while IFS= read -r email; do
  [[ -z "$email" || "$email" == \#* ]] && continue

  response=$(curl -s -o /dev/null -w "%{http_code}" \
    -X POST "https://api.ahasend.com/v2/accounts/$ACCOUNT_ID/suppressions" \
    -H "Authorization: Bearer $API_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"email\": \"$email\", \"expires_at\": \"$EXPIRES_AT\", \"reason\": \"Imported from previous ESP\"}")

  if [ "$response" == "201" ]; then
    echo "Suppressed: $email"
  elif [ "$response" == "409" ]; then
    echo "Already suppressed: $email"
  else
    echo "Failed ($response): $email"
  fi

done < emails.txt
```

A 409 response means the address is already suppressed - that's not an error, just skip it and move on. For CSV files with multiple columns, use the Python script below instead.

## Importing with Python

For larger lists or when you need more control, Python with `httpx` or `requests` is a better fit. This example reads a CSV with email addresses and optional reasons, and imports them with basic rate limiting:

```python
import csv
import time
import httpx

API_KEY = "YOUR_API_KEY"
ACCOUNT_ID = "YOUR_ACCOUNT_ID"
EXPIRES_AT = "2035-01-01T00:00:00Z"
RATE_LIMIT_DELAY = 0.05  # 50ms between requests = ~20 req/s

def import_suppressions(csv_path: str) -> None:
    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    }

    with httpx.Client(timeout=10) as client:
        with open(csv_path, newline="") as f:
            reader = csv.DictReader(f)
            for row in reader:
                email = row.get("email", "").strip()
                reason = row.get("reason", "Imported from previous ESP").strip()

                if not email:
                    continue

                payload = {
                    "email": email,
                    "expires_at": EXPIRES_AT,
                    "reason": reason,
                }

                try:
                    response = client.post(
                        f"https://api.ahasend.com/v2/accounts/{ACCOUNT_ID}/suppressions",
                        headers=headers,
                        json=payload,
                    )

                    if response.status_code == 201:
                        print(f"Suppressed: {email}")
                    elif response.status_code == 409:
                        print(f"Already suppressed: {email}")
                    elif response.status_code == 429:
                        print("Rate limited - waiting 5 seconds...")
                        time.sleep(5)
                    else:
                        print(f"Failed ({response.status_code}): {email} - {response.text}")

                except httpx.RequestError as e:
                    print(f"Request error for {email}: {e}")

                time.sleep(RATE_LIMIT_DELAY)

if __name__ == "__main__":
    import_suppressions("suppressions.csv")
```

Expected CSV format:

```plaintext
email,reason
bounced@example.com,Hard bounce
complaint@example.com,Spam complaint
invalid@example.com,Invalid address
```

## Importing with the Go SDK

If you're already using the AhaSend Go SDK, you can integrate suppression imports directly into your codebase:

```go
package main

import (
    "context"
    "encoding/csv"
    "fmt"
    "log"
    "os"
    "time"

    "github.com/AhaSend/ahasend-go"
    "github.com/AhaSend/ahasend-go/api"
    "github.com/AhaSend/ahasend-go/models/requests"
    "github.com/google/uuid"
)

func main() {
    client := api.NewAPIClient(
        api.WithAPIKey(os.Getenv("AHASEND_API_KEY")),
    )

    accountID := uuid.MustParse(os.Getenv("AHASEND_ACCOUNT_ID"))
    expiresAt := time.Date(2035, 1, 1, 0, 0, 0, 0, time.UTC)

    f, err := os.Open("suppressions.csv")
    if err != nil {
        log.Fatalf("Failed to open CSV: %v", err)
    }
    defer f.Close()

    reader := csv.NewReader(f)
    records, err := reader.ReadAll()
    if err != nil {
        log.Fatalf("Failed to read CSV: %v", err)
    }

    // Skip header row
    for _, record := range records[1:] {
        if len(record) < 1 || record[0] == "" {
            continue
        }

        email := record[0]
        reason := "Imported from previous ESP"
        if len(record) > 1 && record[1] != "" {
            reason = record[1]
        }

        _, _, err := client.SuppressionsAPI.CreateSuppression(
            context.Background(),
            accountID,
            requests.CreateSuppressionRequest{
                Email:     email,
                Reason:    ahasend.String(reason),
                ExpiresAt: expiresAt,
            },
        )

        if err != nil {
            fmt.Printf("Failed to suppress %s: %v\n", email, err)
            continue
        }

        fmt.Printf("Suppressed: %s\n", email)
        time.Sleep(50 * time.Millisecond)
    }
}
```

## Rate limits

The Suppressions API falls under AhaSend's general API rate limit. For large imports, add a short delay between requests (50ms gives you roughly 20 requests per second, which is safe). If you receive a 429 response, back off for a few seconds before retrying.

For very large lists (tens of thousands of addresses), consider running the import in batches overnight or outside peak hours.

## Verifying the import

Once your import is complete, check your suppression list via the API:

```bash
curl "https://api.ahasend.com/v2/accounts/YOUR_ACCOUNT_ID/suppressions?limit=100" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

The response includes pagination - use the `next_cursor` value to page through your full list if you have more than 100 suppressions.

## Further reading

- [Suppressions API reference](https://ahasend.com/docs/api-reference/suppressions/create-suppression)
- [List Suppressions](https://ahasend.com/docs/api-reference/suppressions/get-suppressions)
- [Delete Suppression](https://ahasend.com/docs/api-reference/suppressions/delete-suppression)
- [Wipe Suppressions](https://ahasend.com/docs/api-reference/suppressions/wipe-suppressions)
