--- 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)