or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

configuration-sets.mdemail-sending.mdidentity-management.mdindex.mdreceipt-processing.mdstatistics-monitoring.mdtemplate-management.mdutilities.md
tile.json

utilities.mddocs/

Utilities

Pagination and waiters utilities for handling large result sets and waiting for asynchronous operations to complete. This module provides helper functions for working with SES operations that may return paginated results or require polling.

Capabilities

Pagination

Handle large result sets that are returned across multiple API calls.

/**
 * Configuration for SES pagination operations
 */
interface SESPaginationConfiguration extends PaginationConfiguration {
  client: SESClient;
}

/**
 * Paginate through list of verified identities
 * @param config - Pagination configuration with SES client
 * @param input - List identities input parameters
 * @returns AsyncIterable paginator for identities
 */
function paginateListIdentities(
  config: SESPaginationConfiguration,
  input: ListIdentitiesCommandInput
): Paginator<ListIdentitiesCommandOutput>;

/**
 * Paginate through custom verification email templates
 * @param config - Pagination configuration with SES client
 * @param input - List templates input parameters
 * @returns AsyncIterable paginator for templates
 */
function paginateListCustomVerificationEmailTemplates(
  config: SESPaginationConfiguration,
  input: ListCustomVerificationEmailTemplatesCommandInput
): Paginator<ListCustomVerificationEmailTemplatesCommandOutput>;

Usage Example:

import { 
  SESClient, 
  paginateListIdentities,
  paginateListCustomVerificationEmailTemplates 
} from "@aws-sdk/client-ses";

const client = new SESClient({ region: "us-east-1" });

// Paginate through all identities
const identityPaginator = paginateListIdentities(
  { client },
  { IdentityType: "EmailAddress", MaxItems: 10 }
);

console.log("All verified email addresses:");
for await (const page of identityPaginator) {
  if (page.Identities) {
    page.Identities.forEach(identity => {
      console.log(`- ${identity}`);
    });
  }
}

// Paginate through custom verification templates
const templatePaginator = paginateListCustomVerificationEmailTemplates(
  { client },
  { MaxResults: 5 }
);

console.log("\nCustom verification email templates:");
for await (const page of templatePaginator) {
  if (page.CustomVerificationEmailTemplates) {
    page.CustomVerificationEmailTemplates.forEach(template => {
      console.log(`- ${template.TemplateName}: ${template.TemplateSubject}`);
    });
  }
}

Advanced Pagination Usage

Working with pagination configuration and custom processing.

// Example of using pagination with custom configuration
import { 
  SESClient,
  paginateListIdentities,
  ListIdentitiesCommandInput 
} from "@aws-sdk/client-ses";

const client = new SESClient({ region: "us-east-1" });

async function getAllIdentitiesWithDetails() {
  const paginationConfig = {
    client,
    pageSize: 50, // Custom page size
  };
  
  const input: ListIdentitiesCommandInput = {
    IdentityType: "Domain", // Only domains
  };
  
  const identities: string[] = [];
  const paginator = paginateListIdentities(paginationConfig, input);
  
  try {
    for await (const page of paginator) {
      if (page.Identities) {
        identities.push(...page.Identities);
        console.log(`Loaded ${page.Identities.length} identities (total: ${identities.length})`);
      }
    }
    
    return identities;
  } catch (error) {
    console.error("Error paginating identities:", error);
    throw error;
  }
}

// Usage
const allDomains = await getAllIdentitiesWithDetails();
console.log(`Found ${allDomains.length} verified domains`);

Waiters

Wait for asynchronous operations to complete with built-in retry logic.

/**
 * Waiter configuration for SES operations
 */
interface WaiterConfiguration<T> {
  client: T;
  maxWaitTime?: number;
  minDelay?: number;
  maxDelay?: number;
}

/**
 * Waits for identity verification to complete (deprecated)
 * @param params - Waiter configuration
 * @param input - Identity verification input
 * @returns Promise that resolves when identity is verified
 * @deprecated Use waitForIdentityExists instead
 */
function waitForIdentityExists(
  params: WaiterConfiguration<SESClient>,
  input: GetIdentityVerificationAttributesCommandInput
): Promise<WaiterResult>;

/**
 * Waits for identity verification to complete
 * @param params - Waiter configuration
 * @param input - Identity verification input
 * @returns Promise that resolves when identity is verified
 * @throws WaiterTimeoutError if verification doesn't complete within maxWaitTime
 */
function waitForIdentityExists(
  params: WaiterConfiguration<SESClient>,
  input: GetIdentityVerificationAttributesCommandInput
): Promise<WaiterResult>;

Usage Example:

import { 
  SESClient, 
  VerifyEmailIdentityCommand,
  waitForIdentityExists,
  WaiterTimeoutError 
} from "@aws-sdk/client-ses";

const client = new SESClient({ region: "us-east-1" });

async function verifyEmailAndWait(emailAddress: string) {
  console.log(`Starting verification for ${emailAddress}`);
  
  // Start verification process
  const verifyCommand = new VerifyEmailIdentityCommand({
    EmailAddress: emailAddress,
  });
  await client.send(verifyCommand);
  
  console.log("Verification email sent. Waiting for completion...");
  
  try {
    // Wait for verification to complete
    const waiterResult = await waitForIdentityExists(
      {
        client,
        maxWaitTime: 300, // 5 minutes
        minDelay: 3,      // 3 seconds minimum delay
        maxDelay: 30,     // 30 seconds maximum delay
      },
      {
        Identities: [emailAddress],
      }
    );
    
    if (waiterResult.state === "SUCCESS") {
      console.log(`✅ ${emailAddress} verified successfully!`);
      return true;
    } else {
      console.log(`❌ Verification failed for ${emailAddress}`);
      return false;
    }
  } catch (error) {
    if (error instanceof WaiterTimeoutError) {
      console.log(`⏰ Verification timed out for ${emailAddress}`);
      console.log("The user may not have clicked the verification link yet.");
      return false;
    } else {
      console.error("Unexpected error during verification wait:", error);
      throw error;
    }
  }
}

// Usage
const success = await verifyEmailAndWait("user@example.com");
if (success) {
  console.log("Email is now ready for sending!");
}

Domain Verification with Waiter

Complete example of domain verification with DNS setup and waiting.

import { 
  SESClient, 
  VerifyDomainIdentityCommand,
  waitForIdentityExists 
} from "@aws-sdk/client-ses";

async function verifyDomainWithWaiter(domain: string) {
  const client = new SESClient({ region: "us-east-1" });
  
  console.log(`Starting domain verification for ${domain}`);
  
  // 1. Start domain verification
  const verifyCommand = new VerifyDomainIdentityCommand({
    Domain: domain,
  });
  const verifyResult = await client.send(verifyCommand);
  
  console.log("DNS Setup Required:");
  console.log(`Add this TXT record to ${domain}:`);
  console.log(`Name: _amazonses.${domain}`);
  console.log(`Value: ${verifyResult.VerificationToken}`);
  console.log("\nWaiting for DNS propagation and verification...");
  
  // 2. Wait for verification (this may take a while for domains)
  try {
    const waiterResult = await waitForIdentityExists(
      {
        client,
        maxWaitTime: 900, // 15 minutes for DNS propagation
        minDelay: 10,     // 10 seconds minimum delay
        maxDelay: 60,     // 60 seconds maximum delay
      },
      {
        Identities: [domain],
      }
    );
    
    if (waiterResult.state === "SUCCESS") {
      console.log(`✅ Domain ${domain} verified successfully!`);
      
      // 3. Optionally set up DKIM
      console.log("Setting up DKIM for better deliverability...");
      
      const { VerifyDomainDkimCommand } = await import("@aws-sdk/client-ses");
      const dkimCommand = new VerifyDomainDkimCommand({ Domain: domain });
      const dkimResult = await client.send(dkimCommand);
      
      console.log("DKIM Setup Required:");
      console.log("Add these CNAME records:");
      dkimResult.DkimTokens?.forEach((token, index) => {
        console.log(`${token}._domainkey.${domain} -> ${token}.dkim.amazonses.com`);
      });
      
      return true;
    } else {
      console.log(`❌ Domain verification failed for ${domain}`);
      return false;
    }
  } catch (error) {
    console.error("Error during domain verification:", error);
    return false;
  }
}

// Usage
const domainVerified = await verifyDomainWithWaiter("example.com");
if (domainVerified) {
  console.log("Domain is ready for sending emails!");
}

Core Types

Pagination Types

interface PaginationConfiguration {
  client: SESClient;
  pageSize?: number;
}

interface Paginator<T> extends AsyncIterable<T> {
  [Symbol.asyncIterator](): AsyncIterator<T>;
}

Waiter Types

interface WaiterResult {
  state: WaiterState;
  reason?: string;
}

type WaiterState = "SUCCESS" | "FAILURE" | "RETRY" | "TIMEOUT";

class WaiterTimeoutError extends Error {
  name: "TimeoutError";
  constructor(message: string);
}

Utility Functions

Batch Operations Helper

Process multiple operations with proper error handling and rate limiting.

// Example utility for batch operations (not part of AWS SDK)

interface BatchOperationOptions {
  batchSize: number;
  delayBetweenBatches: number;
  maxRetries: number;
}

async function batchVerifyIdentities(
  client: SESClient,
  identities: string[],
  options: BatchOperationOptions = {
    batchSize: 5,
    delayBetweenBatches: 1000,
    maxRetries: 3,
  }
) {
  const results: Array<{ identity: string; success: boolean; error?: string }> = [];
  
  // Process in batches to respect rate limits
  for (let i = 0; i < identities.length; i += options.batchSize) {
    const batch = identities.slice(i, i + options.batchSize);
    
    const batchPromises = batch.map(async (identity) => {
      let attempts = 0;
      
      while (attempts < options.maxRetries) {
        try {
          const isEmail = identity.includes('@');
          
          if (isEmail) {
            const { VerifyEmailIdentityCommand } = await import("@aws-sdk/client-ses");
            await client.send(new VerifyEmailIdentityCommand({
              EmailAddress: identity,
            }));
          } else {
            const { VerifyDomainIdentityCommand } = await import("@aws-sdk/client-ses");
            await client.send(new VerifyDomainIdentityCommand({
              Domain: identity,
            }));
          }
          
          return { identity, success: true };
        } catch (error) {
          attempts++;
          if (attempts >= options.maxRetries) {
            return { 
              identity, 
              success: false, 
              error: error instanceof Error ? error.message : String(error)
            };
          }
          
          // Wait before retry
          await new Promise(resolve => setTimeout(resolve, 1000 * attempts));
        }
      }
    });
    
    const batchResults = await Promise.all(batchPromises);
    results.push(...batchResults);
    
    // Delay between batches
    if (i + options.batchSize < identities.length) {
      await new Promise(resolve => setTimeout(resolve, options.delayBetweenBatches));
    }
  }
  
  return results;
}

// Usage
const identities = ["user1@example.com", "user2@example.com", "example.com"];
const results = await batchVerifyIdentities(client, identities);

console.log("Batch verification results:");
results.forEach(result => {
  if (result.success) {
    console.log(`✅ ${result.identity}: Verification started`);
  } else {
    console.log(`❌ ${result.identity}: ${result.error}`);
  }
});

Complete Verification Workflow

import { 
  SESClient, 
  VerifyDomainIdentityCommand,
  VerifyDomainDkimCommand,
  waitForIdentityExists,
  paginateListIdentities 
} from "@aws-sdk/client-ses";

const client = new SESClient({ region: "us-east-1" });

async function completeVerificationSetup(domain: string) {
  console.log("=== Complete SES Domain Setup ===\n");
  
  // 1. Check if already verified
  console.log("1. Checking existing verification status...");
  
  const identityPaginator = paginateListIdentities(
    { client },
    { IdentityType: "Domain" }
  );
  
  let alreadyVerified = false;
  for await (const page of identityPaginator) {
    if (page.Identities?.includes(domain)) {
      alreadyVerified = true;
      break;
    }
  }
  
  if (alreadyVerified) {
    console.log(`✅ ${domain} is already verified`);
  } else {
    // 2. Start verification
    console.log("2. Starting domain verification...");
    const verifyResult = await client.send(new VerifyDomainIdentityCommand({
      Domain: domain,
    }));
    
    console.log(`📋 DNS Record Required:`);
    console.log(`   Type: TXT`);
    console.log(`   Name: _amazonses.${domain}`);
    console.log(`   Value: ${verifyResult.VerificationToken}`);
    
    // 3. Wait for verification
    console.log("\n3. Waiting for DNS propagation and verification...");
    console.log("   (This may take several minutes)");
    
    try {
      await waitForIdentityExists(
        { client, maxWaitTime: 900, minDelay: 15, maxDelay: 60 },
        { Identities: [domain] }
      );
      console.log(`✅ ${domain} verified successfully!`);
    } catch (error) {
      console.log(`⏰ Verification timed out. Check DNS record and try again later.`);
      return false;
    }
  }
  
  // 4. Set up DKIM
  console.log("\n4. Setting up DKIM authentication...");
  const dkimResult = await client.send(new VerifyDomainDkimCommand({
    Domain: domain,
  }));
  
  console.log("📋 DKIM CNAME Records Required:");
  dkimResult.DkimTokens?.forEach((token, index) => {
    console.log(`   ${index + 1}. ${token}._domainkey.${domain} -> ${token}.dkim.amazonses.com`);
  });
  
  console.log(`\n🎉 Setup complete for ${domain}!`);
  console.log("Next steps:");
  console.log("1. Add the DKIM CNAME records to your DNS");
  console.log("2. Enable DKIM signing once DNS propagates");
  console.log("3. Configure Mail-From domain (optional)");
  console.log("4. Set up bounce/complaint handling");
  
  return true;
}

// Usage
completeVerificationSetup("mycompany.com").catch(console.error);