Skip to content

How to: Batch Delete multiple Sendgrid Contacts #974

@SeanKilleen

Description

@SeanKilleen

Alright, I've got to share some bewilderment over how great an AI tool was.

Quick Disclaimer

I am not generally a fan of AI. I do not believe it should be used for general knowledge, I think it has eroded some of the foundation of what it means to communicate with one another as humans, I think our society is ill-prepared for it and therefore it's being misused all over the place to great harm, and I think it's impossible to justify the amount of money and natural resources it takes to operate it. But since it's here, I'm choosing to get limited practical usage out of it. And technically speaking, some of its results are pretty impressive.

Goal

I'd just sent an e-mail via SendGrid to 1,000+ contacts from a dated list from one of our tools. ~300 of them bounced. I took the time to collect those. Now, I needed to remove them from our SendGrid contacts.

Challenge

SendGrid's UI is brutal and makes you search for individual contacts and remove them.

But, they have API methods to find contact IDs and then to delete by contact IDs. Unfortunately, there's no easy to way to go from e-mail -> deleted contact.

Solution

I used https://Claude.ai -- in THREE (3!!!) prompts, it generated a 140-line powershell script that did exactly what I needed it to do.

Prompt 1:

I'd like compose a powershell script. This script should have parameters "RecipientsToRemoveFileLocation" and "SendgridAPIKey". The powershell script should read the text of the file at the location specified by RecipientsToRemoveFileLocation (there will be one line per item). The script should then break those items into batches of 5. For each batch, the script should make an http POST request to https://api.sendgrid.com/v3/marketing/contacts/search with a JSON object has a field called query. The query field should have the text "email IN", followed by a parenthetical list of the batch items, where each item is enclosed in single quotes.

Prompt 2:

Instead of Write-Host "Response: $($response | ConvertTo-Json -Depth 3)", capture the list of "id" fields in the "result" array of the http result, and write them out

Prompt 3:

capture all the IDs from all the responses in an array. Then, prompt the user for confirmation. Once the user confirms, in batches of 5, call https://api.sendgrid.com/v3/marketing/contacts with a querystring called "ids", the contents of which are the batch of IDs separated by a comma.

The Result

This script worked the first time, beautifully. I verified it by removing over 300 contacts.

Generated this Powershell script:

param(
    [Parameter(Mandatory=$true)]
    [string]$RecipientsToRemoveFileLocation,
    
    [Parameter(Mandatory=$true)]
    [string]$SendgridAPIKey
)

# Verify the file exists
if (-not (Test-Path -Path $RecipientsToRemoveFileLocation)) {
    Write-Error "File not found: $RecipientsToRemoveFileLocation"
    exit 1
}

# Read recipients from the specified file
$recipients = Get-Content -Path $RecipientsToRemoveFileLocation

# Check if any recipients were found
if (-not $recipients -or $recipients.Count -eq 0) {
    Write-Warning "No recipients found in the file."
    exit 0
}

Write-Host "Found $($recipients.Count) recipients to process"

# Set up HTTP headers for SendGrid API
$headers = @{
    "Authorization" = "Bearer $SendgridAPIKey"
    "Content-Type" = "application/json"
}

# Array to collect all contact IDs
$allContactIds = @()

# Process search in batches of 5
$searchBatchSize = 5
$totalSearchBatches = [Math]::Ceiling($recipients.Count / $searchBatchSize)
$processed = 0

Write-Host "Step 1: Searching for contacts matching the email addresses..."

for ($i = 0; $i -lt $recipients.Count; $i += $searchBatchSize) {
    # Create a batch of up to 5 recipients
    $batch = $recipients[$i..([Math]::Min($i + $searchBatchSize - 1, $recipients.Count - 1))]
    
    # Create the query string with emails in single quotes
    $quotedEmails = $batch | ForEach-Object { "'$_'" }
    $emailList = $quotedEmails -join ", "
    $query = "email IN ($emailList)"
    
    # Create the JSON payload
    $payload = @{
        query = $query
    } | ConvertTo-Json
    
    $batchNumber = [Math]::Floor($i / $searchBatchSize) + 1
    Write-Host "Processing search batch $batchNumber of $totalSearchBatches - Query: $query"
    
    try {
        # Make the POST request to SendGrid API
        $response = Invoke-RestMethod -Uri "https://api.sendgrid.com/v3/marketing/contacts/search" `
                                      -Method Post `
                                      -Headers $headers `
                                      -Body $payload
        
        # Extract the 'id' fields from the result array
        $batchIds = $response.result | ForEach-Object { $_.id }
        
        # Add the IDs to our collection
        $allContactIds += $batchIds
        
        # Output the batch status
        Write-Host "Batch $batchNumber successfully processed"
        Write-Host "IDs found: $($batchIds -join ', ')"
        
        $processed += $batch.Count
        Write-Host "Progress: $processed of $($recipients.Count) processed"
        
        # Add a small delay to avoid rate limiting (adjust as needed)
        Start-Sleep -Milliseconds 500
    }
    catch {
        Write-Error "Error processing search batch ${batchNumber}: $_"
        Write-Error "Error details: $($_.Exception)"
    }
}

Write-Host "Search processing complete. Total recipients processed: $processed"
Write-Host "Total contact IDs found: $($allContactIds.Count)"

# If no IDs were found, exit
if ($allContactIds.Count -eq 0) {
    Write-Warning "No contact IDs found matching the provided email addresses."
    exit 0
}

# Prompt for confirmation before deletion
$confirmation = Read-Host "Do you want to proceed with deleting these $($allContactIds.Count) contacts? (y/n)"

if ($confirmation.ToLower() -ne "y") {
    Write-Host "Operation cancelled by user."
    exit 0
}

# Process deletion in batches of 5
$deleteBatchSize = 5
$totalDeleteBatches = [Math]::Ceiling($allContactIds.Count / $deleteBatchSize)
$deletedCount = 0

Write-Host "Step 2: Deleting contacts..."

for ($i = 0; $i -lt $allContactIds.Count; $i += $deleteBatchSize) {
    # Create a batch of up to 5 IDs
    $idBatch = $allContactIds[$i..([Math]::Min($i + $deleteBatchSize - 1, $allContactIds.Count - 1))]
    
    # Create comma-separated list of IDs
    $idsList = $idBatch -join ","
    
    $batchNumber = [Math]::Floor($i / $deleteBatchSize) + 1
    Write-Host "Processing deletion batch $batchNumber of $totalDeleteBatches"
    
    try {
        # Make the DELETE request to SendGrid API
        $response = Invoke-RestMethod -Uri "https://api.sendgrid.com/v3/marketing/contacts?ids=$idsList" `
                                      -Method Delete `
                                      -Headers $headers
        
        $deletedCount += $idBatch.Count
        Write-Host "Successfully deleted batch $batchNumber"
        Write-Host "Progress: $deletedCount of $($allContactIds.Count) contacts deleted"
        
        # Add a small delay to avoid rate limiting (adjust as needed)
        Start-Sleep -Milliseconds 500
    }
    catch {
        Write-Error "Error deleting batch ${batchNumber}: $_"
        Write-Error "Error details: $($_.Exception)"
    }
}

Write-Host "Deletion complete. Total contacts deleted: $deletedCount"

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions