diff --git a/apps/web/src/app/(dashboard)/domains/[domainId]/delete-domain.tsx b/apps/web/src/app/(dashboard)/domains/[domainId]/delete-domain.tsx index b5e8fab9..ae9dacc7 100644 --- a/apps/web/src/app/(dashboard)/domains/[domainId]/delete-domain.tsx +++ b/apps/web/src/app/(dashboard)/domains/[domainId]/delete-domain.tsx @@ -33,6 +33,9 @@ export const DeleteDomain: React.FC<{ domain: Domain }> = ({ domain }) => { toast.success(`Domain ${domain.name} deleted`); router.replace("/domains"); }, + onError: (error) => { + toast.error(`Failed to delete domain: ${error.message}`); + }, }, ); } diff --git a/apps/web/src/app/(dashboard)/domains/domain-list.tsx b/apps/web/src/app/(dashboard)/domains/domain-list.tsx index a2ef5f4e..58254cc7 100644 --- a/apps/web/src/app/(dashboard)/domains/domain-list.tsx +++ b/apps/web/src/app/(dashboard)/domains/domain-list.tsx @@ -9,6 +9,7 @@ import React from "react"; import { StatusIndicator } from "./status-indicator"; import { DomainStatusBadge } from "./domain-badge"; import Spinner from "@usesend/ui/src/spinner"; +import { DeleteDomain } from "./[domainId]/delete-domain"; export default function DomainsList() { const domainsQuery = api.domain.domains.useQuery(); @@ -116,6 +117,9 @@ const DomainItem: React.FC<{ domain: Domain }> = ({ domain }) => { /> +
+ +
diff --git a/apps/web/src/server/aws/ses.ts b/apps/web/src/server/aws/ses.ts index 0fda94c8..62d4d0d6 100644 --- a/apps/web/src/server/aws/ses.ts +++ b/apps/web/src/server/aws/ses.ts @@ -150,30 +150,67 @@ export async function deleteDomain( const sesClient = getSesClient(region); if (sesTenantId) { - const tenantResourceAssociationCommand = - new DeleteTenantResourceAssociationCommand({ - TenantName: sesTenantId, - ResourceArn: await getIdentityArn(domain, region), - }); + try { + const tenantResourceAssociationCommand = + new DeleteTenantResourceAssociationCommand({ + TenantName: sesTenantId, + ResourceArn: await getIdentityArn(domain, region), + }); + + const tenantResourceAssociationResponse = await sesClient.send( + tenantResourceAssociationCommand + ); - const tenantResourceAssociationResponse = await sesClient.send( - tenantResourceAssociationCommand - ); + if (tenantResourceAssociationResponse.$metadata.httpStatusCode !== 200) { + logger.error( + { domain, region, sesTenantId, tenantResourceAssociationResponse }, + "[ses.deleteDomain] non-200 from DeleteTenantResourceAssociation" + ); + throw new Error("Failed to delete tenant resource association"); + } + } catch (error: any) { + if (error?.name === "NotFoundException") { + logger.warn( + { domain, region, sesTenantId, errorName: error.name }, + "[ses.deleteDomain] tenant association already gone, continuing" + ); + } else { + logger.error( + { err: error, domain, region, sesTenantId }, + "[ses.deleteDomain] DeleteTenantResourceAssociation failed" + ); + throw error; + } + } + } - if (tenantResourceAssociationResponse.$metadata.httpStatusCode !== 200) { + try { + const command = new DeleteEmailIdentityCommand({ + EmailIdentity: domain, + }); + const response = await sesClient.send(command); + if (response.$metadata.httpStatusCode !== 200) { logger.error( - { tenantResourceAssociationResponse }, - "Failed to delete tenant resource association" + { domain, region, response }, + "[ses.deleteDomain] non-200 from DeleteEmailIdentity" ); - throw new Error("Failed to delete tenant resource association"); + return false; } + return true; + } catch (error: any) { + if (error?.name === "NotFoundException") { + logger.warn( + { domain, region, errorName: error.name }, + "[ses.deleteDomain] identity already gone on SES, continuing" + ); + return true; + } + logger.error( + { err: error, domain, region }, + "[ses.deleteDomain] DeleteEmailIdentity failed" + ); + throw error; } - - const command = new DeleteEmailIdentityCommand({ - EmailIdentity: domain, - }); - const response = await sesClient.send(command); - return response.$metadata.httpStatusCode === 200; } export async function getDomainIdentity(domain: string, region: string) {