Async Email Sending Queue
Queue up bulk emails in Azure Queues and process them one-by-one to avoid throttling and ensure delivery.
Provided as-is, without warranty of any kind. Review and test each pattern in a non-production environment before deploying it to live automations. See our Terms.
Overview
This flow drains an Azure Storage Queue of pending email messages and sends them one at a time through the Office 365 Outlook connector. By queueing email payloads up front and processing them sequentially on a recurring schedule, the pattern decouples the producer from the email transport, smooths out bursts, prevents the Outlook connector throttling limits from killing a high-volume run, and gives every send its own success/failure decision so a single bad recipient never poisons the batch. When all messages succeed it deletes them from the queue and finishes silently. When any send fails, it leaves the failed message in the queue (it becomes visible again after the visibility timeout) and emails an HTML summary to the operations distribution list.
Use Case
Any producer flow, app, or service that needs to "fire and forget" an email writes a JSON payload to a single Azure Storage Queue. The producer never has to worry about Outlook throttling, retries, or the recipient list size — it just appends to the queue. This flow handles delivery reliability.
The flow is ideal for teams that:
- A canvas app that emails a generated PDF to several stakeholders after a Submit click — the app drops one queue message per recipient and returns to the user instantly.
- A nightly Power BI dataflow that emails personalised stat lines to ~500 sales reps. Putting them on a queue and letting this flow sip them at five-minute intervals keeps the run inside the Outlook connector's per-minute limit.
- A Dataverse plugin that wants to send a welcome email outside its 2-minute transaction window — it enqueues a message, returns, and lets this flow handle delivery.
Flow Architecture
Recurrence Every 5 Minutes
RecurrenceBuilt-in Recurrence trigger (Eastern Standard Time) that polls the queue every 5 minutes. Adjust to taste.
Initialize Storage Account Name
Initialize variableSets varStorageAccount from the AzureQueueStorageAccountName environment variable parameter.
Initialize Queue Name
Initialize variableSets varQueueName from the AsyncEmailQueueName environment variable parameter.
Initialize Ops Team Email
Initialize variableSets varOpsTeamEmail from the OperationsTeamEmail environment variable parameter.
Initialize Sent Count
Initialize variableInteger counter starting at 0 — tallies successful sends across the run.
Initialize Failed Count
Initialize variableInteger counter starting at 0 — tallies failed sends across the run.
Initialize Error Details
Initialize variableEmpty string accumulator for failure HTML — gets appended to with one <li> per failed send.
Get Pending Email Messages
Azure Queues — GetMessages_V2Pulls up to 32 messages (numofmessages: 32) and hides them from other consumers for 60 seconds (visibilitytimeout: 60). If the run crashes mid-send, messages automatically reappear.
For Each Email Message
Foreach (sequential)Iterates over @coalesce(body('Get_Pending_Email_Messages')?['QueueMessagesList']?['QueueMessage'], createArray()) with concurrency.repetitions: 1. Sequential is critical for avoiding Outlook throttling and keeping pop receipts valid. Each iteration runs Parse Email Payload, a Try Send Email scope, and an If Send Succeeded condition (yes branch: increment sent count + delete the message via DeleteMessage_V2 using MessageId and PopReceipt; no branch: increment failed count + append an HTML <li> with recipient, subject, and the scope's error message). The If tests result('Try_Send_Email')[0]['status'] equals 'Succeeded' with runAfter [Succeeded, Failed, TimedOut, Skipped].
Environment Variables
| Schema name | Type | Default | Description |
|---|---|---|---|
| flowlibs_AzureQueueStorageAccountName | String | <configure> | Storage account name that hosts the email queue. Set to your Azure Storage account name (e.g. mycompanyqueues). |
| flowlibs_AsyncEmailQueueName | String | flowlibs-async-email-queue | Name of the Azure Storage Queue this flow drains. The queue must already exist in the storage account before the flow runs. |
| flowlibs_OperationsTeamEmail | String | operations@your-tenant.onmicrosoft.com | Recipient (single mailbox or distribution group) for failure summary emails. Set to the address that should receive run-failure notifications. |
Connectors & Connections
| Connector | API name | Actions used |
|---|---|---|
| Azure Queues | shared_azurequeues | GetMessages_V2 (Pulls up to 32 pending email payloads per run) DeleteMessage_V2 (Deletes each successfully sent message by MessageId + PopReceipt) |
| Office 365 Outlook | shared_office365 | SendEmailV2 (Sends each queued email and the failure summary email) |
Note — All connections are referenced as solution connection references; the flow is portable between environments as long as a connection is mapped at import time.
Customization Guide
Almost every realistic variant of this flow can be implemented by changing environment variable values. A few cases require small edits inside the flow definition — those are called out explicitly below.
- Polling cadence
- Edit the Recurrence trigger interval. For quasi-real-time delivery, drop to 1 minute. For overnight bulk runs, switch to a Day-frequency trigger with startTime set to the desired window.
- Throughput per run
- Bump Get Pending Email Messages numofmessages up to 32 (the connector cap) or down to slow the drain. The For Each is sequential, so total per-run runtime ≈ messages × seconds-per-send.
- Visibility timeout
- Get Pending Email Messages uses visibilitytimeout: 60 so a crashed run releases the message after one minute. Increase if individual sends are slow (large attachments) so the message doesn't reappear before the original send finishes.
- Recipient mailbox
- Reauthorize the Send Email To Recipient and Send Failure Summary Email actions against a shared mailbox to send from a generic address (Send as permissions required on the mailbox).
- Dead-letter handling
- A failed message is left in the queue and will reappear after 60 seconds, then be retried on the next scheduled run. To cap retries, add a dequeueCount check and route over-the-cap messages to a separate dead-letter queue.
- Producer payload format
- JSON: {"to": "alice@example.com", "subject": "Hello", "htmlBody": "<p>Body</p>", "importance": "Normal", "cc": "team@example.com"}. cc is optional; the wired-up Send Email currently ignores it — add an emailMessage/Cc parameter to surface it.
- Run summary recipients
- Switch flowlibs_OperationsTeamEmail to a distribution group to fan-out failure alerts.
Key Expressions
The flow is intentionally light on Power Fx / WDL gymnastics — the heaviest expressions are the branch-name concatenation and the approval outcome check. They are listed below in the order they appear in the flow.
EXPR.01Defensive Foreach iterator
Wraps the Azure Queues connector's nested response shape and tolerates an empty queue without throwing on length(null).
EXPR.02Per-iteration message body
Pulls the JSON payload string for each iteration; piped into ParseJson.
EXPR.03Pop receipt for delete
Both values are required by DeleteMessage_V2; the pop receipt expires when the visibility timeout elapses.
EXPR.04Scope status check
Inspects the Try Send Email scope's first action result — Succeeded / Failed / TimedOut / Skipped. Decouples branching from the Send Email's specific output shape.
EXPR.05Safe payload defaults
Coalesce defaults around every payload field so a malformed message doesn't blow up the email send — it sends with empty fields and lets the failure branch capture it.
EXPR.06Inner Outlook error message
Reads the inner Outlook error message for the failure summary HTML.
Comments
Sign in to join the conversation.
Sign inNo comments yet. Be the first to share your experience with this flow.