Hi Matt Worsley,
ACS uses shared access keys under the hood to sign each request with a timestamp, and the backend checks that the time difference is within a 5-minute window. If your EmailClient
instance is long-lived, or if there’s a delay between token creation and request execution (like retries or queue delays), that timestamp can go stale — and ACS rejects it.
- Rather than reusing a single client instance across all sends, I now just create a new one per email (or per small batch). That ensures the timestamp used for signing is always fresh and reduces the chance of drift.
const emailClient = new EmailClient(process.env.EMAIL_CONNECTION_STRING);
const poller = await emailClient.beginSend(message);
await poller.pollUntilDone();
- If you’re in a position to use Azure AD (via
DefaultAzureCredential
orClientSecretCredential
), do it. These tokens are refreshed automatically and are much more resilient under load. You’ll avoid the time skew problem entirely.
const credential = new DefaultAzureCredential();
const emailClient = new EmailClient(endpoint, credential);
Unfortunately, ACS doesn’t support batch email sends yet — each beginSend()
call is for one email. So you’ve got to optimize around single sends.
Hope it helps!
Please do not forget to click "Accept the answer” and Yes
wherever the information provided helps you, this can be beneficial to other community members.
If you have any other questions or still running into more issues, let me know in the "comments" and I would be happy to help you.