AhaSend
Terug naar Blog

Een suppressielijst importeren doe je zo

Mark Kraakman
Mark Kraakman
Handleidingen

Als je migreert van een andere ESP of je suppressielijst wilt vullen met adressen die nooit een e-mail mogen ontvangen, kun je dat via de Suppressions API van AhaSend programmatisch doen. Deze gids laat zien hoe je een suppressielijst bulk-importeert met cURL, Python en de Go SDK.

Wat is een supressielijst?

Een suppressielijst is je overzicht van e-mailadressen waar je niet naar moet versturen - harde bounces, spamklachten, handmatige uitschrijvingen, of adressen waarvan je weet dat ze ongeldig zijn. Versturen naar onderdrukte adressen schaadt je verzendreputatie en kan bij harde bounces en klachten in strijd zijn met de gebruiksvoorwaarden van je ESP.

Bij migratie van een andere provider is het importeren van je bestaande suppressielijst vóór het versturen van je eerste e-mail een van de belangrijkste stappen die je kunt nemen. Sla je dit over, dan herhaal je de bounces en klachten die die lijst hebben opgebouwd.

Je suppressielijst exporteren uit een andere provider

Voordat je kunt importeren in AhaSend, moet je je bestaande suppressielijst uit je huidige ESP halen.

  • SendGrid: Gebruik het Retrieve all suppressions-endpoint om alle onderdrukte adressen via de API te exporteren.
  • Postmark: Ga naar het tabblad Suppressions van je Message Stream en klik op Export, of gebruik de Suppressions API om een dump programmatisch op te halen.

Zodra je je lijst met adressen hebt, sla je ze op in een tekstbestand met één e-mailadres per regel en volg je de stappen hieronder.

De API

Elke suppressie wordt afzonderlijk aangemaakt via een POST-verzoek. Voor bulk-imports loop je over je lijst en roep je het endpoint één keer per adres aan.

POST /v2/accounts/{account_id}/suppressions

Verplichte velden:

  • email - het te onderdrukken adres (moet een geldig e-mailadres zijn)
  • expires_at - wanneer de suppressie verloopt (RFC3339-formaat)

Optionele velden:

  • domain - beperk de suppressie tot een specifiek verzenddomein. Als dit wordt weggelaten, geldt de suppressie voor alle domeinen op het account.
  • reason - een notitie over waarom het adres is onderdrukt (max 255 tekens)

Als je permanente suppressies importeert (harde bounces, spamklachten), stel expires_at dan ver in de toekomst in - bijvoorbeeld 10 jaar vanaf nu. AhaSend vereist een vervaldatum, maar niets weerhoudt je ervan die ruim buiten de verwachte levensduur van je product te stellen.

Importeren met cURL

Voor kleine lijsten of eenmalige imports is cURL de snelste optie:

curl -X POST https://api.ahasend.com/v2/accounts/JOUW_ACCOUNT_ID/suppressions \
  -H "Authorization: Bearer JOUW_API_SLEUTEL" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "expires_at": "2035-01-01T00:00:00Z",
    "reason": "Hard bounce"
  }'

Voor een grotere lijst gebruik je een tekstbestand met één e-mailadres per regel (geen headers, geen komma's):

[email protected]
[email protected]
[email protected]
#!/bin/bash
API_KEY="JOUW_API_SLEUTEL"
ACCOUNT_ID="JOUW_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\": \"Geïmporteerd van vorige ESP\"}")

  if [ "$response" == "201" ]; then
    echo "Onderdrukt: $email"
  elif [ "$response" == "409" ]; then
    echo "Al onderdrukt: $email"
  else
    echo "Mislukt ($response): $email"
  fi

done < emails.txt

Een 409-respons betekent dat het adres al is onderdrukt - dat is geen fout, sla het gewoon over. Voor CSV-bestanden met meerdere kolommen gebruik je het Python-script hieronder.

Importeren met Python

Voor grotere lijsten of wanneer je meer controle nodig hebt, is Python met httpx of requests een betere keuze. Dit voorbeeld leest een CSV met e-mailadressen en optionele redenen, en importeert ze met eenvoudige rate limiting:

import csv
import time
import httpx

API_KEY = "JOUW_API_SLEUTEL"
ACCOUNT_ID = "JOUW_ACCOUNT_ID"
EXPIRES_AT = "2035-01-01T00:00:00Z"
RATE_LIMIT_DELAY = 0.05  # 50ms tussen verzoeken = ~20 verzoeken/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", "Geïmporteerd van vorige 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"Onderdrukt: {email}")
                    elif response.status_code == 409:
                        print(f"Al onderdrukt: {email}")
                    elif response.status_code == 429:
                        print("Rate limit bereikt - 5 seconden wachten...")
                        time.sleep(5)
                    else:
                        print(f"Mislukt ({response.status_code}): {email} - {response.text}")

                except httpx.RequestError as e:
                    print(f"Verbindingsfout voor {email}: {e}")

                time.sleep(RATE_LIMIT_DELAY)

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

Verwacht CSV-formaat:

email,reason
[email protected],Hard bounce
[email protected],Spamklacht
[email protected],Ongeldig adres

Importeren met de Go SDK

Als je de AhaSend Go SDK al gebruikt, kun je suppression-imports rechtstreeks in je codebase integreren:

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("CSV openen mislukt: %v", err)
    }
    defer f.Close()

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

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

        email := record[0]
        reason := "Geïmporteerd van vorige 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("Onderdrukken mislukt voor %s: %v\n", email, err)
            continue
        }

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

Rate limits

De Suppressions API valt onder de algemene API-rate limit van AhaSend. Voeg voor grote imports een korte vertraging toe tussen verzoeken (50ms geeft je ongeveer 20 verzoeken per seconde, wat veilig is). Als je een 429-respons ontvangt, wacht dan een paar seconden voor je het opnieuw probeert.

Voor zeer grote lijsten (tienduizenden adressen) kun je de import het beste in batches uitvoeren, 's nachts of buiten de piekuren.

De import verifiëren

Zodra je import klaar is, controleer je je suppressielijst via de API:

curl "https://api.ahasend.com/v2/accounts/JOUW_ACCOUNT_ID/suppressions?limit=100" \
  -H "Authorization: Bearer JOUW_API_SLEUTEL"

De respons bevat paginering - gebruik de next_cursor-waarde om door je volledige lijst te bladeren als je meer dan 100 suppressies hebt.

Meer lezen