Merge branch 'dev' into promptless/document-cloud-run-deployment
Some checks failed
DB migration compat / Check if migrations changed (push) Has been cancelled
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
DB migration compat / No migration changes (skipped) (push) Has been cancelled

This commit is contained in:
promptless[bot] 2026-04-24 20:36:10 +00:00
commit 51ea75b83b
6 changed files with 789 additions and 424 deletions

View File

@ -181,6 +181,22 @@ export async function updateManagedEmailDomainWebhookStatus(options: {
return mapRow(rows[0]!);
}
export async function demoteOtherAppliedManagedEmailDomains(options: {
tenancyId: string,
keepId: string,
}): Promise<void> {
await globalPrismaClient.$queryRaw(Prisma.sql`
UPDATE "ManagedEmailDomain"
SET
"status" = 'VERIFIED'::"ManagedEmailDomainStatus",
"appliedAt" = NULL,
"updatedAt" = CURRENT_TIMESTAMP
WHERE "tenancyId" = ${options.tenancyId}
AND "id" <> ${options.keepId}
AND "status" = 'APPLIED'::"ManagedEmailDomainStatus"
`);
}
export async function markManagedEmailDomainApplied(id: string): Promise<ManagedEmailDomain> {
const rows = await globalPrismaClient.$queryRaw<ManagedEmailDomainRow[]>(Prisma.sql`
UPDATE "ManagedEmailDomain"

View File

@ -3,6 +3,7 @@ import {
ManagedEmailDomain,
ManagedEmailDomainStatus,
createManagedEmailDomain,
demoteOtherAppliedManagedEmailDomains,
getManagedEmailDomainByResendDomainId,
getManagedEmailDomainByTenancyAndSubdomain,
listManagedEmailDomainsForTenancy,
@ -12,6 +13,7 @@ import {
import { Tenancy } from "@/lib/tenancies";
import { getNodeEnvironment, getEnvVariable } from "@stackframe/stack-shared/dist/utils/env";
import { StackAssertionError, StatusError } from "@stackframe/stack-shared/dist/utils/errors";
import { runAsynchronously, wait } from "@stackframe/stack-shared/dist/utils/promises";
type ResendDomainRecord = {
record: string,
@ -543,7 +545,16 @@ export async function setupManagedEmailProvider(options: { subdomain: string, se
senderLocalPart: options.senderLocalPart,
resendDomainId: `managed_mock_${options.tenancy.id}_${normalizedSubdomain}`.replace(/[^a-zA-Z0-9_-]/g, "_"),
nameServerRecords: ["ns1.dnsimple.com", "ns2.dnsimple.com"],
status: "verified",
status: "pending_verification",
});
runAsynchronously(async () => {
await wait(1000);
await updateManagedEmailDomainWebhookStatus({
resendDomainId: row.resendDomainId,
providerStatusRaw: "verified",
status: "verified",
lastError: null,
});
});
return managedDomainToSetupResult(row);
}
@ -631,6 +642,10 @@ export async function applyManagedEmailProvider(options: {
senderLocalPart: domain.senderLocalPart,
});
await demoteOtherAppliedManagedEmailDomains({
tenancyId: options.tenancy.id,
keepId: domain.id,
});
await markManagedEmailDomainApplied(domain.id);
return { status: "applied" };
}

View File

@ -0,0 +1,3 @@
<svg width="1800" height="1800" viewBox="0 0 1800 1800" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1000.46 450C1174.77 450 1278.43 553.669 1278.43 691.282C1278.43 828.896 1174.77 932.563 1000.46 932.563H912.382L1350 1350H1040.82L707.794 1033.48C683.944 1011.47 672.936 985.781 672.935 963.765C672.935 932.572 694.959 905.049 737.161 893.122L908.712 847.244C973.85 829.812 1018.81 779.353 1018.81 713.298C1018.8 632.567 952.745 585.78 871.095 585.78H450V450H1000.46Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 504 B

View File

@ -0,0 +1,3 @@
<svg width="1800" height="1800" viewBox="0 0 1800 1800" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1000.46 450C1174.77 450 1278.43 553.669 1278.43 691.282C1278.43 828.896 1174.77 932.563 1000.46 932.563H912.382L1350 1350H1040.82L707.794 1033.48C683.944 1011.47 672.936 985.781 672.935 963.765C672.935 932.572 694.959 905.049 737.161 893.122L908.712 847.244C973.85 829.812 1018.81 779.353 1018.81 713.298C1018.8 632.567 952.745 585.78 871.095 585.78H450V450H1000.46Z" fill="#FDFDFD"/>
</svg>

After

Width:  |  Height:  |  Size: 506 B

View File

@ -1,3 +1,4 @@
import { wait } from "@stackframe/stack-shared/dist/utils/promises";
import { describe } from "vitest";
import { it } from "../../../../../helpers";
import { Project, niceBackendFetch } from "../../../../backend-helpers";
@ -48,7 +49,12 @@ describe("managed email onboarding internal endpoints", () => {
expect(setupResponse.status).toBe(200);
expect(setupResponse.body.domain_id).toBeDefined();
expect(setupResponse.body.status).toBe("verified");
expect(setupResponse.body.status).toBe("pending_verification");
// Mock onboarding asynchronously flips status to "verified" ~1s after setup
// (mirroring the real Resend webhook flow). Wait for the transition before
// asserting verified state.
await wait(1500);
const listResponse = await niceBackendFetch("/api/v1/internal/emails/managed-onboarding/list", {
method: "GET",