Stop Sabotaging Your Power Automate Email Flows
Your Power Automate email flows aren’t clever automations, they’re HR risk wrapped in HTML. You wire a flow to a “service account,” fight through MFA once, get it working, and decide it’s done. It’s not done. It’s brittle, over-privileged, impossible to audit cleanly, and the first time conditional access or a password policy changes, it dies loudly or, worse, half-silently.
A service account is still a user. Delegated auth, MFA prompts, password expiry, CA tweaks – all the stuff meant for humans – now sits in front of a robot that can’t tap “Approve” at 2:14 a.m. Tokens expire, flows wake up, find nothing valid, and either fail or thrash until they send duplicates like a jammed label maker. To “fix” that, people hand it Send As, then another exception, then a shared mailbox that never gets revoked. Over time your “one sender” is an impersonation cannon that can send as almost anyone. Now mix in shared passwords and mailbox rights and your audit trail becomes vibes: was it the flow, the admin, the intern’s cached Outlook profile? When regulators or legal ask “who sent this and under what control?” you’ve got shrugs, not evidence.
The root problem is you’re pretending a robot is a person. Applications need their own non-human identity with machine-grade auth and precise, inspectable, revocable permissions. That’s an app registration in Entra with Mail.Send at the application scope, not delegated. The app acts as itself, not as some fake human. But Mail.Send alone is too much if you leave it naked – by default the app could send as any mailbox in the tenant. That’s not “powerful,” that’s an attack surface. So you add the fence: Exchange Online application access policies. You drop your allowed HR sender mailboxes into a mail-enabled security group, then bind the app to that group. From that moment, Exchange is the bouncer: the app shows its badge, Exchange checks whether that mailbox is in the group; if yes, mail goes, if not, 403 and the door stays closed.
Your Power Automate email flows aren’t “automation”—they’re a compliance breach disguised as convenience. If you’re sending HR notifications, offer letters, policy updates, onboarding announcements, or termination messages using a service account, you’re operating outside modern Microsoft 365 architecture and inside constant risk. In this episode, we break down the correct, secure, reliable method:
Microsoft Graph + App Registration + Application Access Policies.
No MFA failures, no expired passwords, no ambiguous audit trails, no over-privileged mailboxes. Just clean, scoped, predictable, enterprise-grade email delivery you can prove during an audit. Whether you’re an IT admin, M365 architect, Power Automate builder, HR systems owner, or security engineer, this episode teaches you how to replace “flow roulette” with a professional, governable pattern. 🔥 What This Episode Covers ✔ Why service accounts destroy reliability and compliance ✔ How Conditional Access, MFA, and password expiry break your flows ✔ Why delegated access is the wrong fit for automation ✔ How App Registrations solve identity, security, and audit traceability ✔ How Application Access Policies fence your app to specific mailboxes ✔ The exact Graph endpoint to use for HR email delivery ✔ Custom Connector schema for reusable, safe sends ✔ The #1 misconfiguration that exposes every mailbox in your tenant ✔ The monitoring + audit strategy that keeps leadership off your back 🧨 Opening Hook: You’re Sabotaging Email Without Realizing It Most organizations use Power Automate email flows the wrong way:
- A service account
- Shared password
- Delegated permissions
- “Send As” rights across multiple mailboxes
- Hard-coded credentials stored in connections
- A fragile MFA exemption nobody documents
That model breaks the moment:
- MFA is enforced
- CA policies change
- Passwords expire
- Tenant restrictions evolve
- A mailbox permission drifts
Your flow fails at 2:14 a.m., tries to retry, and duplicates a dozen HR messages—or worse, stops sending entirely and you don’t know until a VP asks why no one received the policy update. This episode is the intervention your tenant needs. 🧩 Section 1 — Why Service Accounts Sabotage Email Flows In this segment, we break down the four hidden failure modes: 1. Delegated Auth = Silent Fragility User-based tokens expire quickly and require interactive login. Flows can’t respond to MFA prompts.
Result: intermittent failures + random retries + duplicate sends. 2. Over-Privilege Creep To “fix” broken flows, people grant:
- Send As on multiple mailboxes
- Shared mailbox permissions
- “Temporary” access that never gets removed
Suddenly, your “simple” service account can impersonate HR, Finance, Legal, and leadership. 3. Broken Audit Trails Who sent the message?
- The service account?
- The admin who logged in?
- A cached Outlook profile?
- A Power Automate connection owner?
Delegated identity = zero clarity. 4. Conditional Access Drift Any change in CA rules—travel rules, location restrictions, MFA prompts—instantly breaks the flow.
You don’t learn until someone complains. This section establishes the core truth:
Service accounts are human identities pretending to be machines. They fail because the model is wrong. 🚀 Section 2 — The Architecture Microsoft Actually Intended This is the heart of the episode:
App Registration (client credential auth) + Graph Mail.Send + Application Access Policies. ✔ App Registration → non-human identity
- Uses certificate or secret
- Immune to MFA prompts
- Fully auditable
- Predictable token lifecycle
✔ Graph Mail.Send (Application Permission)
- The app sends as the mailbox
- No user delegation
- No shared passwords
- No impersonation ambiguity
✔ Application Access Policies
- The app can only send as the mailboxes you approve
- Everything else = 403 denied
- This prevents accidental tenant-wide impersonation
This architecture gives you:
- Reliability
- Security
- Least privilege
- Clean auditability
- Compliance alignment
- HR-safe email delivery
This is the pattern Microsoft engineering teams use internally. 🛠 Section 3 — Implementation: Locking Down Email the Right Way We detail the full implementation workflow: ✔ Create App Registration ✔ Add Microsoft Graph → “Mail.Send” (Application) ✔ Grant admin consent ✔ Create a mail-enabled security group (e.g., HR-Transactional-Senders) ✔ Add allowed sender mailboxes to that group ✔ Bind the app to the group using Application Access Policy ✔ Test with Test-ApplicationAccessPolicy ✔ Send through: POST https://graph.microsoft.com/v1./users/{senderUPN}/sendMail This section explains the exact, real-world PowerShell used:
- New-ApplicationAccessPolicy
- Set-ApplicationAccessPolicy
- Test-ApplicationAccessPolicy
We also explain the “danger path” where skipping this step allows the app to send as ANY mailbox in the tenant—the most common & most dangerous misconfiguration. ⚙️ Section 4 — Graph Email Send Patterns & Payloads You’ll learn:
- JSON payload structure for HR messages
- How to embed HTML templates
- How to manage attachments
- How to use custom headers (X-Trace-Intent, X-Business-Id)
- How to avoid duplicate emails with idempotency keys
- How to handle throttling (429) + Retry-After
- When to use MIME vs JSON
- How to correlate failures with request-id + client-request-id
This section is designed for Power Automate devs, engineers, and security reviewers. 🔌 Section 5 — Custom Connector Schema for Power Automate We describe how to wrap everything into a secure, reusable custom connector: ✔ Base URL: https://graph.microsoft.com ✔ Security: OAuth2 client credentials ✔ Action: sendMail ✔ Path: /v1./users/{sender}/sendMail ✔ Input schema:
- subject
- body
- recipients
- attachments
- internetMessageHeaders
- saveToSentItems
✔ Output schema:
- status
- request-id
- client-request-id
- timestamp
✔ Internal retry logic ✔ Structured error responses ✔ Strong validation This is the “enterprise version” of sending email—a single action, centrally governed, safe by design. 🔍 Section 6 — Monitoring, Auditing, and Incident Prevention This section explains the full audit chain:
- Entra sign-ins → app identity
- Exchange audit → mailbox-level record
- Purview → compliance log
- Graph → request-id tracing
- Log Analytics → central store
We outline: ✔ Alerts for policy violations ✔ Alerts for throttling ✔ Alerts for unauthorized mailbox attempts ✔ Daily delivery dashboards ✔ Rotating secrets/certificates safely ✔ Full runbook for incident response This is how you avoid HR disasters, rogue flows, and compliance failures.
Become a supporter of this podcast: https://www.spreaker.com/podcast/m365-show-podcast--6704921/support.
Follow us on:
LInkedIn
Substack
1
00:00:00,000 --> 00:00:02,560
Your Power Automate emails aren't clever automations.
2
00:00:02,560 --> 00:00:04,440
They're an HR breach waiting to happen.
3
00:00:04,440 --> 00:00:06,280
You glue the flow to a service account,
4
00:00:06,280 --> 00:00:09,120
cross your fingers through MFA prompts and call it done.
5
00:00:09,120 --> 00:00:10,560
That's amateur hour.
6
00:00:10,560 --> 00:00:11,460
Here's the fix.
7
00:00:11,460 --> 00:00:13,720
Microsoft Graph with an app registration,
8
00:00:13,720 --> 00:00:15,640
locked by application access policies,
9
00:00:15,640 --> 00:00:18,720
so the app can only send from the mailboxes you approve.
10
00:00:18,720 --> 00:00:20,280
I'll give you the exact power shell,
11
00:00:20,280 --> 00:00:23,080
the graph endpoints and a custom connector schema.
12
00:00:23,080 --> 00:00:25,800
HR notifications, offers, policy updates,
13
00:00:25,800 --> 00:00:27,800
terminations will deliver reliably
14
00:00:27,800 --> 00:00:30,440
under conditional access MFA and tenant restrictions.
15
00:00:30,440 --> 00:00:33,120
There's one misstep that silently exposes every mailbox,
16
00:00:33,120 --> 00:00:34,200
we'll close it.
17
00:00:34,200 --> 00:00:37,960
Foundation, why service accounts, sabotage email flows.
18
00:00:37,960 --> 00:00:39,520
Let's start with the myth.
19
00:00:39,520 --> 00:00:41,160
A service account is simple.
20
00:00:41,160 --> 00:00:44,000
The truth, delegated auth tied to a human style identity
21
00:00:44,000 --> 00:00:45,120
is fragile by design.
22
00:00:45,120 --> 00:00:48,360
Conditional access changes, MFA challenges
23
00:00:48,360 --> 00:00:50,960
and password expiry aren't edge cases, they're routine.
24
00:00:50,960 --> 00:00:53,960
Your flow can't approve a push notification at 2.14 a.m.
25
00:00:53,960 --> 00:00:56,240
So it chokes, ritris and occasionally sprays duplicates
26
00:00:56,240 --> 00:00:57,880
like a malfunctioning label maker.
27
00:00:57,880 --> 00:01:00,960
You wanted reliability, you built roulette.
28
00:01:00,960 --> 00:01:03,360
Now the over-privileged problem, to make it work,
29
00:01:03,360 --> 00:01:06,360
someone grants the service account sent as on a mailbox.
30
00:01:06,360 --> 00:01:08,360
Then another exception, then temporary access
31
00:01:08,360 --> 00:01:11,160
to a shared mailbox that mysteriously never gets removed.
32
00:01:11,160 --> 00:01:12,880
That creep turns a single-purpose sender
33
00:01:12,880 --> 00:01:15,160
into a tenant-wide impersonation machine.
34
00:01:15,160 --> 00:01:19,480
HR messages must be pristine, who sent what, when and as whom.
35
00:01:19,480 --> 00:01:22,120
With shared passwords and mailbox rights attribution blurs,
36
00:01:22,120 --> 00:01:24,000
was it the flow, the admin, the intern
37
00:01:24,000 --> 00:01:27,240
with the cashed outlook profile, your audit trail is vibes.
38
00:01:27,240 --> 00:01:29,120
Audit blindness is more than annoying.
39
00:01:29,120 --> 00:01:30,480
It's non-compliant.
40
00:01:30,480 --> 00:01:32,920
When an offer letter goes out at 2.14 a.m.,
41
00:01:32,920 --> 00:01:35,080
you need a clean line to the non-human identity
42
00:01:35,080 --> 00:01:37,760
that issued the call plus the policy that allowed it.
43
00:01:37,760 --> 00:01:39,800
Delegated tokens issued to a user account
44
00:01:39,800 --> 00:01:41,240
don't give you that clarity.
45
00:01:41,240 --> 00:01:43,840
And when tenant restrictions evolve, as they do,
46
00:01:43,840 --> 00:01:46,840
delegated flows inherit breakage silently.
47
00:01:46,840 --> 00:01:48,360
You discover the blast crater later
48
00:01:48,360 --> 00:01:50,920
when managers ask why nobody received policy updates
49
00:01:50,920 --> 00:01:52,960
on time, reliability takes a beating too.
50
00:01:52,960 --> 00:01:54,760
Delegated tokens are short-lived and coupled
51
00:01:54,760 --> 00:01:56,040
to interactive behavior.
52
00:01:56,040 --> 00:01:58,160
Flows wake up, find the token expired,
53
00:01:58,160 --> 00:02:00,600
and either fail hard or bounce between retreats
54
00:02:00,600 --> 00:02:02,320
until they duplicate sends.
55
00:02:02,320 --> 00:02:03,640
The help desk gets a ticket.
56
00:02:03,640 --> 00:02:04,720
Governance gets blamed.
57
00:02:04,720 --> 00:02:07,000
Everyone matters about power-automate being flaky
58
00:02:07,000 --> 00:02:08,560
when the problem is your auth model.
59
00:02:08,560 --> 00:02:09,640
And let's talk compliance.
60
00:02:09,640 --> 00:02:11,440
Email isn't a casual transport layer.
61
00:02:11,440 --> 00:02:13,640
It's regulated infrastructure.
62
00:02:13,640 --> 00:02:16,120
HR notices, policy acknowledgments,
63
00:02:16,120 --> 00:02:17,800
and termination communications often
64
00:02:17,800 --> 00:02:20,440
sit inside legal and audit requirements.
65
00:02:20,440 --> 00:02:22,320
Just make it send is not a strategy.
66
00:02:22,320 --> 00:02:23,800
It's negligence with HTML.
67
00:02:23,800 --> 00:02:26,760
Now you might be thinking, but our conditional access
68
00:02:26,760 --> 00:02:28,560
exempts the service account.
69
00:02:28,560 --> 00:02:30,360
Congratulations, you've bypassed the controls
70
00:02:30,360 --> 00:02:31,680
that protect your tenant.
71
00:02:31,680 --> 00:02:33,880
You reduced friction by removing the seat belt,
72
00:02:33,880 --> 00:02:35,760
also, exemptions drift.
73
00:02:35,760 --> 00:02:38,800
A well-meaning change to a CA policy breaks your exception
74
00:02:38,800 --> 00:02:39,920
or worse broadens it.
75
00:02:39,920 --> 00:02:42,360
You don't discover it until the flow phase plans
76
00:02:42,360 --> 00:02:44,680
or start sending from the wrong identity
77
00:02:44,680 --> 00:02:46,840
because someone fixed it at the mailbox level.
78
00:02:46,840 --> 00:02:48,840
Everything changes when you stop pretending a robot
79
00:02:48,840 --> 00:02:49,840
is a person.
80
00:02:49,840 --> 00:02:51,920
Applications need non-human identities
81
00:02:51,920 --> 00:02:54,920
with machine-grade auth, certificates, or client secrets
82
00:02:54,920 --> 00:02:57,160
and permissions that are precise, inspectable,
83
00:02:57,160 --> 00:03:00,000
and revocable without touching user mailboxes.
84
00:03:00,000 --> 00:03:01,280
Enter app registrations.
85
00:03:01,280 --> 00:03:03,560
You grant mail send at the application scope,
86
00:03:03,560 --> 00:03:05,160
no delegated nonsense, and then you
87
00:03:05,160 --> 00:03:07,560
fence that power with application access policies.
88
00:03:07,560 --> 00:03:10,360
So the app can send as only the mailboxes you explicitly
89
00:03:10,360 --> 00:03:12,120
allow, not helpfully all.
90
00:03:12,120 --> 00:03:13,600
Precisely the ones you choose.
91
00:03:13,600 --> 00:03:16,360
This is where attribution and audit finally makes sense.
92
00:03:16,360 --> 00:03:18,320
Graph sign-in logs tie calls to the app.
93
00:03:18,320 --> 00:03:20,680
Exchange and purview trails show the mailbox,
94
00:03:20,680 --> 00:03:22,320
the operation, the time.
95
00:03:22,320 --> 00:03:25,480
You can export diagnostics to log analytics or your CM,
96
00:03:25,480 --> 00:03:28,040
correlate by client request ID and request ID,
97
00:03:28,040 --> 00:03:30,400
and answer the one question audits always ask.
98
00:03:30,400 --> 00:03:33,280
Who did what using which identity under which policy?
99
00:03:33,280 --> 00:03:34,560
And reliability?
100
00:03:34,560 --> 00:03:36,480
Tokens are obtained via client credentials.
101
00:03:36,480 --> 00:03:39,200
No MFA prompts, no browser popups, no passwords,
102
00:03:39,200 --> 00:03:41,200
expiring at midnight on quarter end.
103
00:03:41,200 --> 00:03:44,320
Access tokens live for about an hour, which is predictable.
104
00:03:44,320 --> 00:03:47,840
Your flow requests a new one, sends the mail, captures headers, moves on.
105
00:03:47,840 --> 00:03:49,240
No drama, no duplicates storm.
106
00:03:49,240 --> 00:03:50,520
So here's the foundation.
107
00:03:50,520 --> 00:03:52,360
Service accounts sabotage your flows
108
00:03:52,360 --> 00:03:54,280
because they conflate human authentication
109
00:03:54,280 --> 00:03:57,560
with machine tasks, invite over privilege, erase attribution,
110
00:03:57,560 --> 00:04:00,440
and collapse the second conditional access evolves.
111
00:04:00,440 --> 00:04:02,120
You don't fix that with another exception.
112
00:04:02,120 --> 00:04:04,200
You fix it by giving the machine its own identity
113
00:04:04,200 --> 00:04:06,000
and a fence it can't climb.
114
00:04:06,000 --> 00:04:09,320
Architecture, app registration, plus graph is the professional standard.
115
00:04:09,320 --> 00:04:10,520
Enter app registration.
116
00:04:10,520 --> 00:04:12,640
Not a person in a trench coat pretending to be a robot,
117
00:04:12,640 --> 00:04:14,320
but an actual non-human identity
118
00:04:14,320 --> 00:04:16,360
with its own credentials and permissions.
119
00:04:16,360 --> 00:04:19,200
It authenticates with client credentials, certificate, or secret,
120
00:04:19,200 --> 00:04:21,640
so there's no MFA bingo, no password roulette.
121
00:04:21,640 --> 00:04:24,760
It's predictable and predictability is the oxygen of automation.
122
00:04:24,760 --> 00:04:26,200
You start with least privilege.
123
00:04:26,200 --> 00:04:29,680
And yes, average user that phrase has meaning, grant the app the mail.
124
00:04:29,680 --> 00:04:33,040
Send application permission, application not delegated.
125
00:04:33,040 --> 00:04:35,000
Delegated means act as a user.
126
00:04:35,000 --> 00:04:38,560
Application means act as itself, which is exactly what your flow is itself.
127
00:04:38,560 --> 00:04:40,680
Nothing more, nothing less.
128
00:04:40,680 --> 00:04:44,280
Now mail, send at application scope is powerful, too powerful
129
00:04:44,280 --> 00:04:45,440
if you leave it hanging open.
130
00:04:45,440 --> 00:04:46,120
The truth?
131
00:04:46,120 --> 00:04:49,160
Without a fence, the app can technically send as an email box.
132
00:04:49,160 --> 00:04:51,440
That's not a feature, that's an attack surface.
133
00:04:51,440 --> 00:04:55,160
So we add the fence, application access policies in exchange online.
134
00:04:55,160 --> 00:04:58,280
These policies tell exchange even though the app holds mail.
135
00:04:58,280 --> 00:05:00,760
Send it can only act for these mailboxes.
136
00:05:00,760 --> 00:05:03,960
Think digital keycard program to open only the HR mailboxes,
137
00:05:03,960 --> 00:05:06,360
not the CEOs, not the cafeteria newsletter.
138
00:05:06,360 --> 00:05:07,480
Precision.
139
00:05:07,480 --> 00:05:08,720
The pattern is simple.
140
00:05:08,720 --> 00:05:12,240
Create a mail-enabled security group, call it HR transactional senders
141
00:05:12,240 --> 00:05:13,320
if you must be literal.
142
00:05:13,320 --> 00:05:16,880
Add only the mailboxes that represent your HR communications.
143
00:05:16,880 --> 00:05:20,600
Offers a policy at termination set.
144
00:05:20,600 --> 00:05:22,480
Then bind your app registration to that group
145
00:05:22,480 --> 00:05:24,280
using an application access policy.
146
00:05:24,280 --> 00:05:25,880
Exchange becomes the bouncer.
147
00:05:25,880 --> 00:05:27,280
The app shows its ID.
148
00:05:27,280 --> 00:05:28,720
Exchange checks the list.
149
00:05:28,720 --> 00:05:31,840
If the mailbox isn't in the group 403, door stays closed.
150
00:05:31,840 --> 00:05:33,520
No drama, just enforcement.
151
00:05:33,520 --> 00:05:36,200
Token acquisition uses the client credentials flow.
152
00:05:36,200 --> 00:05:38,160
The access token lives for roughly an hour,
153
00:05:38,160 --> 00:05:41,520
long enough to do useful work, short enough to limit blast radius.
154
00:05:41,520 --> 00:05:44,400
Your flow requests a token, calls graph and moves on.
155
00:05:44,400 --> 00:05:47,800
No pop-ups, no approved sign-in on someone's phone at midnight.
156
00:05:47,800 --> 00:05:50,160
Because repeat after me, it's not a person.
157
00:05:50,160 --> 00:05:51,840
Auditability gets an upgrade.
158
00:05:51,840 --> 00:05:55,320
With graph, the sign-in logs tie activity to the app's service principle.
159
00:05:55,320 --> 00:05:58,440
Exchange audit shows senders with the target mailbox.
160
00:05:58,440 --> 00:06:00,760
Per view holds the compliance breadcrumbs.
161
00:06:00,760 --> 00:06:03,560
You include a client request ID header on every call.
162
00:06:03,560 --> 00:06:05,240
Graph returns a request ID in date.
163
00:06:05,240 --> 00:06:07,680
Those three values are your tracing trinity.
164
00:06:07,680 --> 00:06:09,560
When someone asks who sent that offer,
165
00:06:09,560 --> 00:06:12,720
you answer with a GUID and a timestamp instead of a shrug.
166
00:06:12,720 --> 00:06:14,600
Compare that to your current mess.
167
00:06:14,600 --> 00:06:17,680
Ambiguous shared accounts, fuzzy send-days permissions,
168
00:06:17,680 --> 00:06:21,200
and email headers that read like a crime novel written by committee.
169
00:06:21,200 --> 00:06:23,200
With app registration plus policy scoping,
170
00:06:23,200 --> 00:06:24,920
attribution is machine clean.
171
00:06:24,920 --> 00:06:27,600
You can export enter sign-ins, directory audit,
172
00:06:27,600 --> 00:06:31,040
and exchange logs to log analytics or your seam set alerts for anomalies
173
00:06:31,040 --> 00:06:33,040
and actually see patterns instead of guessing them.
174
00:06:33,040 --> 00:06:35,240
And yes, conditional access, here's the important bit.
175
00:06:35,240 --> 00:06:38,040
Client credentials flows aren't subject to user MFA.
176
00:06:38,040 --> 00:06:39,760
That's not a loophole, it's the design.
177
00:06:39,760 --> 00:06:42,320
You enforce safety at the permission layer and the policy fence,
178
00:06:42,320 --> 00:06:43,840
not with interactive challenges.
179
00:06:43,840 --> 00:06:45,840
Tenant restrictions, they still matter, for example,
180
00:06:45,840 --> 00:06:48,440
blocking legacy auth or constraining app types.
181
00:06:48,440 --> 00:06:50,800
But your app's path is explicit and documentable.
182
00:06:50,800 --> 00:06:53,480
You don't need a special carve-out for a fake user account.
183
00:06:53,480 --> 00:06:56,160
You need the right app permissions and the exchange access policy.
184
00:06:56,160 --> 00:06:59,640
That's governance, not vibes, reliability steps forward, too.
185
00:06:59,640 --> 00:07:03,240
Because the app identity doesn't expire its password like a board human.
186
00:07:03,240 --> 00:07:06,720
You're not scheduling a quarterly who broke the flow of investigation.
187
00:07:06,720 --> 00:07:08,800
Certificates can be rotated on schedule.
188
00:07:08,800 --> 00:07:11,840
Secrets can be stored in a vault and rolled with change control.
189
00:07:11,840 --> 00:07:14,240
The flow keeps running because the contract is clear.
190
00:07:14,240 --> 00:07:17,120
App proves itself, exchange enforces scope,
191
00:07:17,120 --> 00:07:20,880
graph delivers the payload, HR use case mapping becomes straightforward.
192
00:07:20,880 --> 00:07:22,560
The security group is your ring fence.
193
00:07:22,560 --> 00:07:24,880
Need to add a new sender for onboarding season.
194
00:07:24,880 --> 00:07:26,440
Add the mailbox to the group.
195
00:07:26,440 --> 00:07:28,040
Need to revoke after a campaign.
196
00:07:28,040 --> 00:07:31,160
Remove it, no reprimissioning the app, no editing ten flows.
197
00:07:31,160 --> 00:07:33,040
The boundary lives where it belongs.
198
00:07:33,040 --> 00:07:36,240
In exchanges policy, visible, auditable and testable,
199
00:07:36,240 --> 00:07:39,360
essentially this architecture separates concerns cleanly.
200
00:07:39,360 --> 00:07:41,240
Identity, app registration,
201
00:07:41,240 --> 00:07:44,360
capability, mail, send at application scope,
202
00:07:44,360 --> 00:07:46,440
guard rail, application access policy,
203
00:07:46,440 --> 00:07:49,000
restricting target mailboxes, transport,
204
00:07:49,000 --> 00:07:51,120
Microsoft graph, telemetry,
205
00:07:51,120 --> 00:07:54,520
entra, exchange, purview, all stitched with request IDs.
206
00:07:54,520 --> 00:07:57,280
It's boring in the best way like a well run airport.
207
00:07:57,280 --> 00:08:00,440
Plains land, planes take off, nobody argues with the runway lights.
208
00:08:00,440 --> 00:08:01,800
This is the professional standard,
209
00:08:01,800 --> 00:08:03,760
not because it's trendy, but because it's resilient,
210
00:08:03,760 --> 00:08:05,320
inspecable and reversible.
211
00:08:05,320 --> 00:08:07,280
You can prove who can send, who did send,
212
00:08:07,280 --> 00:08:10,560
and who can't with a configuration diff instead of folklore.
213
00:08:10,560 --> 00:08:12,320
If you insist on service accounts,
214
00:08:12,320 --> 00:08:13,800
you're announcing to your future self
215
00:08:13,800 --> 00:08:15,840
that you prefer outages with side effects.
216
00:08:15,840 --> 00:08:18,720
Use an app, fence it, log it, then sleep.
217
00:08:18,720 --> 00:08:20,920
Implementation part one, lock down sending
218
00:08:20,920 --> 00:08:22,840
with application access policies.
219
00:08:22,840 --> 00:08:24,240
Okay, so here's the build.
220
00:08:24,240 --> 00:08:25,880
We're giving the machine an identity
221
00:08:25,880 --> 00:08:29,240
and we're fencing it so it can only send from HR approved mailboxes.
222
00:08:29,240 --> 00:08:31,080
Precision first, convenience later.
223
00:08:31,080 --> 00:08:33,640
Step one, create the app and grant the right permission.
224
00:08:33,640 --> 00:08:36,160
In Microsoft, enter a register a new application,
225
00:08:36,160 --> 00:08:40,000
call it HR transactional mail if names help you think straight.
226
00:08:40,000 --> 00:08:43,120
Capture the application client ID and directory tenant ID
227
00:08:43,120 --> 00:08:46,600
add a client credential ID Leon X 509 certificate,
228
00:08:46,600 --> 00:08:48,520
find a secret if you must,
229
00:08:48,520 --> 00:08:51,520
stored in a vault and rotated like a grownup.
230
00:08:51,520 --> 00:08:55,040
Under API permissions at Microsoft Graph application permissions mail,
231
00:08:55,040 --> 00:08:59,160
send then grant admin consent, not delegated application
232
00:08:59,160 --> 00:09:01,600
because the app is sending as itself under policy,
233
00:09:01,600 --> 00:09:03,040
not borrowing a human.
234
00:09:03,040 --> 00:09:05,120
Step two, create the ring fence.
235
00:09:05,120 --> 00:09:07,600
In exchange online, you need a mail enabled security group
236
00:09:07,600 --> 00:09:09,440
that represents allowed senders.
237
00:09:09,440 --> 00:09:12,680
Name it HR transactional senders add only the mailboxes
238
00:09:12,680 --> 00:09:15,320
you want the app to act for offers at Contoso,
239
00:09:15,320 --> 00:09:18,920
come policy at contoso.com terminations at contoso.com.
240
00:09:18,920 --> 00:09:21,360
Yes, just those, not everyone for convenience.
241
00:09:21,360 --> 00:09:23,280
That's how incidents are manufactured.
242
00:09:23,280 --> 00:09:25,200
Step three, bind the app to the fence
243
00:09:25,200 --> 00:09:26,840
with an application access policy.
244
00:09:26,840 --> 00:09:29,840
This is the perimeter fire up exchange online power shell.
245
00:09:29,840 --> 00:09:32,280
You'll need the apps service principle ID.
246
00:09:32,280 --> 00:09:36,720
That's the enterprise application object in entra then new application
247
00:09:36,720 --> 00:09:40,240
access policy, app ID, policy scope group ID,
248
00:09:40,240 --> 00:09:43,680
HR transactional senders at contoso.com.
249
00:09:43,680 --> 00:09:47,680
Access write, restrict access, description, HR send scope.
250
00:09:47,680 --> 00:09:49,800
That single command is the difference between mail,
251
00:09:49,800 --> 00:09:51,760
send can impersonate the tenant and mail,
252
00:09:51,760 --> 00:09:53,920
send can only send us these mailboxes.
253
00:09:53,920 --> 00:09:55,400
Exchange is now the bouncer.
254
00:09:55,400 --> 00:09:57,680
The app shows its badge, exchange checks the list,
255
00:09:57,680 --> 00:10:00,960
you verify the policy with test application access policy.
256
00:10:00,960 --> 00:10:02,920
Example, test application access policy,
257
00:10:02,920 --> 00:10:04,960
app ID identity offers at contoso.
258
00:10:04,960 --> 00:10:07,840
Compton sort expect access check result granted.
259
00:10:07,840 --> 00:10:11,720
Then try a mailbox not in the group test application access policy,
260
00:10:11,720 --> 00:10:16,080
app ID identity CEO at contoso.com.expect access check result.
261
00:10:16,080 --> 00:10:18,120
Denied if you see granted where you shouldn't,
262
00:10:18,120 --> 00:10:19,240
you did something wrong.
263
00:10:19,240 --> 00:10:21,000
Fix it before congratulating yourself.
264
00:10:21,000 --> 00:10:24,280
Now align with conditional access and MFA expectations.
265
00:10:24,280 --> 00:10:26,120
Client credentials aren't interactive.
266
00:10:26,120 --> 00:10:30,160
CA policies targeting user sign-in controls and MFA prompts
267
00:10:30,160 --> 00:10:32,720
don't apply to an app asserting its own identity.
268
00:10:32,720 --> 00:10:35,200
That's not a gap, that's the model, documented.
269
00:10:35,200 --> 00:10:36,920
Your tenant restrictions still matter.
270
00:10:36,920 --> 00:10:39,600
Block legacy protocols restrict app consent to admins,
271
00:10:39,600 --> 00:10:42,840
require certificates for confidential clients if you're serious.
272
00:10:42,840 --> 00:10:46,040
The key is you're not creating risky user exceptions.
273
00:10:46,040 --> 00:10:48,920
You're using an app with least privilege and an exchange fence.
274
00:10:48,920 --> 00:10:51,560
Let's talk PowerShell prerequisites so you don't trip.
275
00:10:51,560 --> 00:10:53,800
Use the Exchange Online Management module.
276
00:10:53,800 --> 00:10:55,480
Connect Exchange Online with an account
277
00:10:55,480 --> 00:10:58,000
that can manage application access policies.
278
00:10:58,000 --> 00:11:00,080
Ensure the group is mail enabled and resolvable
279
00:11:00,080 --> 00:11:03,480
by primary SMTP address in policy scope group ID.
280
00:11:03,480 --> 00:11:06,680
And yes, the app it in the policy can be the application client ID.
281
00:11:06,680 --> 00:11:08,920
Exchange figures out the service principle.
282
00:11:08,920 --> 00:11:11,200
If you've duplicated objects across directories,
283
00:11:11,200 --> 00:11:11,880
that's on you.
284
00:11:11,880 --> 00:11:13,120
Keep environments clean.
285
00:11:13,120 --> 00:11:14,600
Graph tracing is not optional.
286
00:11:14,600 --> 00:11:17,600
Every send request should include a client request ID header,
287
00:11:17,600 --> 00:11:18,960
a guide you generate.
288
00:11:18,960 --> 00:11:21,960
Graph will echo back request ID and date.
289
00:11:21,960 --> 00:11:23,880
In your flows capture both response headers
290
00:11:23,880 --> 00:11:25,600
and store them in your error log.
291
00:11:25,600 --> 00:11:28,400
Request ID, client request ID, date,
292
00:11:28,400 --> 00:11:30,760
sender, recipients outcome.
293
00:11:30,760 --> 00:11:33,200
When an HR manager says, did the offer go?
294
00:11:33,200 --> 00:11:33,720
You don't guess.
295
00:11:33,720 --> 00:11:36,000
You search by GUID, reconcile with enter sign-ins
296
00:11:36,000 --> 00:11:38,160
and exchange audit and answer with receipts.
297
00:11:38,160 --> 00:11:40,480
Now test the perimeter with real calls.
298
00:11:40,480 --> 00:11:43,520
Acquire a token via client credentials for HTTPS
299
00:11:43,520 --> 00:11:46,720
thus slash graph, Microsoft.com default.
300
00:11:46,720 --> 00:11:50,520
Attempt, post, v1, users offers at contoso.com,
301
00:11:50,520 --> 00:11:52,200
send mail with a minimal payload.
302
00:11:52,200 --> 00:11:52,760
It should work.
303
00:11:52,760 --> 00:11:54,720
Attempt the same call with CEO at contoso.
304
00:11:54,720 --> 00:11:55,240
Come.
305
00:11:55,240 --> 00:11:57,360
You should get 403 with an error code
306
00:11:57,360 --> 00:12:00,800
like error access denied or authorization request denied.
307
00:12:00,800 --> 00:12:02,160
That's the policy doing its job.
308
00:12:02,160 --> 00:12:04,080
If you see 401, your token is wrong.
309
00:12:04,080 --> 00:12:06,560
If you see 403 on an allowed mailbox,
310
00:12:06,560 --> 00:12:08,600
your policy scope or group membership is wrong
311
00:12:08,600 --> 00:12:10,320
or you forgot admin consent.
312
00:12:10,320 --> 00:12:11,200
This isn't magic.
313
00:12:11,200 --> 00:12:12,080
It's plumbing.
314
00:12:12,080 --> 00:12:13,560
Check each joint.
315
00:12:13,560 --> 00:12:15,600
Document tenant alignment decisions.
316
00:12:15,600 --> 00:12:19,040
Write down that CA/MFA don't gate client credentials.
317
00:12:19,040 --> 00:12:20,680
That risk is mitigated by mail.
318
00:12:20,680 --> 00:12:22,320
Send at application scope,
319
00:12:22,320 --> 00:12:24,000
plus an application access policy,
320
00:12:24,000 --> 00:12:25,880
restricting target mailboxes.
321
00:12:25,880 --> 00:12:29,160
Note secret or certificate storage, key vault,
322
00:12:29,160 --> 00:12:32,000
rotation cadence, and who can modify the policy.
323
00:12:32,000 --> 00:12:34,160
If governance can't read it, it didn't happen.
324
00:12:34,160 --> 00:12:36,200
A quick micro story to keep you honest.
325
00:12:36,200 --> 00:12:39,720
Last week a team asked why their HR flow randomly failed.
326
00:12:39,720 --> 00:12:41,800
The service account password expired.
327
00:12:41,800 --> 00:12:42,960
When they rushed to fix it,
328
00:12:42,960 --> 00:12:46,560
someone gave it send A's on a shared mailbox used for newsletters.
329
00:12:46,560 --> 00:12:49,320
Within hours automated HR mail and marketing mail blended,
330
00:12:49,320 --> 00:12:50,160
audit was a mess.
331
00:12:50,160 --> 00:12:52,080
We shifted them to an app added mail,
332
00:12:52,080 --> 00:12:54,720
send, created the HR transactional senders group,
333
00:12:54,720 --> 00:12:57,560
bound the policy and spoiler traceability returned.
334
00:12:57,560 --> 00:12:59,040
That's the power of the fans.
335
00:12:59,040 --> 00:13:00,640
Finally regression proofing.
336
00:13:00,640 --> 00:13:02,400
After you create or change the policy,
337
00:13:02,400 --> 00:13:06,520
run test application access policy for each HR mailbox in scope
338
00:13:06,520 --> 00:13:07,840
and a few out of scope.
339
00:13:07,840 --> 00:13:09,520
Bake those tests into a runbook.
340
00:13:09,520 --> 00:13:11,160
On secret or certificate rotation day,
341
00:13:11,160 --> 00:13:13,480
validate token acquisition, then run policy tests,
342
00:13:13,480 --> 00:13:16,360
then send a deliberate test mail with a unique header.
343
00:13:16,360 --> 00:13:18,600
Xtrace intent, HR policy check.
344
00:13:18,600 --> 00:13:20,560
So you can find it in headers and logs.
345
00:13:20,560 --> 00:13:21,880
If you think this is overkill,
346
00:13:21,880 --> 00:13:24,160
you've never explained a breach to legal.
347
00:13:24,160 --> 00:13:27,520
Parameter set app identity in place, exchange and forcing scope.
348
00:13:27,520 --> 00:13:30,840
Next, we wire the actual send over graph, clean payloads,
349
00:13:30,840 --> 00:13:33,280
clean retries, no connectors yet.
350
00:13:33,280 --> 00:13:36,960
Implementation part two, graph email, send patterns and endpoints.
351
00:13:36,960 --> 00:13:38,800
Now we orchestrate the actual send.
352
00:13:38,800 --> 00:13:42,760
No connectors, no mystery, just HTTP against graph with headers you control.
353
00:13:42,760 --> 00:13:45,280
Core endpoint first, post-heart TPS such graph,
354
00:13:45,280 --> 00:13:49,800
Microsoft.com, where you want users or sender VPN, send mail.
355
00:13:49,800 --> 00:13:52,480
Replace sender VPN with the allowed mailbox,
356
00:13:52,480 --> 00:13:55,120
like offers@contoso.com/tun,
357
00:13:55,120 --> 00:13:56,840
this endpoint sends us that mailbox,
358
00:13:56,840 --> 00:13:59,480
which is exactly why the application access policy matters.
359
00:13:59,480 --> 00:14:03,680
Your body is a JSON envelope called message plus a save to send items flag.
360
00:14:03,680 --> 00:14:05,280
The minimal payload looks like this,
361
00:14:05,280 --> 00:14:07,400
conceptually not code, theater, message,
362
00:14:07,400 --> 00:14:11,880
subject message, body with content, type HTML or text and content message.
363
00:14:11,880 --> 00:14:14,920
To recipients as an array of objects with email address.
364
00:14:14,920 --> 00:14:18,160
Add CC recipients and BCC recipients arrays when needed.
365
00:14:18,160 --> 00:14:20,040
Save to send items, true for HR,
366
00:14:20,040 --> 00:14:21,720
so send items show the record.
367
00:14:21,720 --> 00:14:24,000
Faults only if you've planned an alternate archive.
368
00:14:24,000 --> 00:14:25,400
Attachments come in two flavors.
369
00:14:25,400 --> 00:14:28,720
For most HR scenarios offer letters, policy PDFs,
370
00:14:28,720 --> 00:14:31,680
use JSON attachments with content bytes as base64.
371
00:14:31,680 --> 00:14:35,560
Each item has name, content type and content bytes.
372
00:14:35,560 --> 00:14:38,200
Keep individual attachments sane, graph is happy,
373
00:14:38,200 --> 00:14:40,280
but your recipients gateways are not.
374
00:14:40,280 --> 00:14:42,760
If you need full MIME control, custom headers,
375
00:14:42,760 --> 00:14:45,160
S-MIME payloads, exact line endings,
376
00:14:45,160 --> 00:14:47,960
you pivot to send mail with a MIME message in base64,
377
00:14:47,960 --> 00:14:51,400
via the me sent mail equivalent for app as user scenarios,
378
00:14:51,400 --> 00:14:54,960
or you use users' pop-osh ID messages with create plus send.
379
00:14:54,960 --> 00:14:58,520
For 99% of HR automation, the JSON model is simpler and safer.
380
00:14:58,520 --> 00:14:59,760
Headers matter.
381
00:14:59,760 --> 00:15:01,760
Always include client request ID as a guide,
382
00:15:01,760 --> 00:15:03,720
you generate per call and prefer.
383
00:15:03,720 --> 00:15:06,920
Outlook, time zone if you care about date normalization on drafts,
384
00:15:06,920 --> 00:15:08,920
though for send mail, it's less relevant.
385
00:15:08,920 --> 00:15:11,480
Capture response headers request ID and date.
386
00:15:11,480 --> 00:15:14,120
These are your correlation anchors across graph,
387
00:15:14,120 --> 00:15:16,680
enter assignings and exchange audit.
388
00:15:16,680 --> 00:15:19,560
Now the unpleasant but necessary part, rate limiting.
389
00:15:19,560 --> 00:15:23,400
When graph returns 429, it's not a suggestion, it's a command.
390
00:15:23,400 --> 00:15:25,240
Respect, retry after in seconds.
391
00:15:25,240 --> 00:15:27,720
Implement exponential backoff that starts with retry after
392
00:15:27,720 --> 00:15:30,920
if present otherwise a sane default 24/8 seconds with jitter.
393
00:15:30,920 --> 00:15:34,200
Item potency is your shield against duplicate HR emails.
394
00:15:34,200 --> 00:15:38,040
Generate a stable item potency key per intended outbound message,
395
00:15:38,040 --> 00:15:40,840
compose it from the business object ID plus recipient
396
00:15:40,840 --> 00:15:43,320
and a timestamp bucket and store it.
397
00:15:43,320 --> 00:15:46,680
If a retry happens, check whether you've already recorded a successful send
398
00:15:46,680 --> 00:15:48,440
for that key before you fire again.
399
00:15:48,440 --> 00:15:51,480
You're preventing the two offers, one candidate embarrassment.
400
00:15:51,480 --> 00:15:53,000
Error classes are predictable.
401
00:15:53,000 --> 00:15:55,720
401 means your token is missing or expired.
402
00:15:55,720 --> 00:15:59,320
Acquire a new access token using client credentials and replay once.
403
00:15:59,320 --> 00:16:01,000
403 is policy or permission.
404
00:16:01,000 --> 00:16:04,120
Decode the error.code in the JSON, authorization request,
405
00:16:04,120 --> 00:16:07,080
denied or error access denied usually means your mailbox
406
00:16:07,080 --> 00:16:09,400
isn't in the application access policy group
407
00:16:09,400 --> 00:16:11,160
or admin consent didn't happen.
408
00:16:11,160 --> 00:16:12,920
Do not retry a 403.
409
00:16:12,920 --> 00:16:16,040
Fix configuration 404 on the user path usually means
410
00:16:16,040 --> 00:16:18,040
you misspelled the sender UPN.
411
00:16:18,040 --> 00:16:19,800
5x is transit service wobble.
412
00:16:19,800 --> 00:16:22,760
Retry with back off over a new HTTP connection.
413
00:16:22,760 --> 00:16:27,240
Your flow should treat 429 and 5xx as recoverable within a small window.
414
00:16:27,240 --> 00:16:28,520
Everything else escalates.
415
00:16:28,520 --> 00:16:29,400
Security hygiene.
416
00:16:29,400 --> 00:16:31,400
Do not log bodies with PII.
417
00:16:31,400 --> 00:16:32,520
Yes, average user.
418
00:16:32,520 --> 00:16:34,280
This includes the entire offer letter.
419
00:16:34,280 --> 00:16:35,240
Log metadata.
420
00:16:35,240 --> 00:16:37,880
Client request ID, request ID, date, sender,
421
00:16:37,880 --> 00:16:41,000
recipient count, attachment count, outcome and any error.
422
00:16:41,000 --> 00:16:42,520
Code plus a redacted message.
423
00:16:42,520 --> 00:16:44,600
If you must keep a copy of the send content,
424
00:16:44,600 --> 00:16:48,120
rely on the mailboxes, send items or a governed archive.
425
00:16:48,120 --> 00:16:49,160
Not your flow logs.
426
00:16:49,160 --> 00:16:49,880
S-mime.
427
00:16:49,880 --> 00:16:52,680
If your HR notices require signing or encryption,
428
00:16:52,680 --> 00:16:53,880
you're in MIME land.
429
00:16:53,880 --> 00:16:55,640
You'll build the full MIME payload
430
00:16:55,640 --> 00:16:57,160
with the appropriate content type
431
00:16:57,160 --> 00:17:01,000
and transfer encoded as base 64 within the raw message.
432
00:17:01,000 --> 00:17:03,720
Application permissions can still send.
433
00:17:03,720 --> 00:17:07,080
The complexity lives in building the MIME correctly
434
00:17:07,080 --> 00:17:08,360
and managing certificates.
435
00:17:08,360 --> 00:17:10,200
For most internal policy updates,
436
00:17:10,200 --> 00:17:14,440
TLS@transportplusDKIM plus D-mark alignment is sufficient.
437
00:17:14,840 --> 00:17:17,800
Save S-mime for regulated cases where a signature is mandated.
438
00:17:17,800 --> 00:17:19,720
HR payload patterns should be templated,
439
00:17:19,720 --> 00:17:21,160
not hand-built per run.
440
00:17:21,160 --> 00:17:24,360
Store HTML templates with token placeholders, candidate name,
441
00:17:24,360 --> 00:17:26,600
start date, manager name, policy link.
442
00:17:26,600 --> 00:17:29,800
Replace at runtime, sanitize inputs and include a lightweight
443
00:17:29,800 --> 00:17:32,120
X-trace header via internet message headers
444
00:17:32,120 --> 00:17:33,480
for internal correlation.
445
00:17:33,480 --> 00:17:37,000
Like X-trace intent, HR offer, X-business id,
446
00:17:37,000 --> 00:17:38,200
12345.
447
00:17:38,200 --> 00:17:40,680
Keep custom headers minimal, some gateways
448
00:17:40,680 --> 00:17:41,880
waste strip or rewrite them.
449
00:17:41,880 --> 00:17:42,600
That's fine.
450
00:17:42,600 --> 00:17:43,960
You log the IDs already.
451
00:17:43,960 --> 00:17:45,880
Identity deserves one more line.
452
00:17:45,880 --> 00:17:48,760
Use a stable deterministic key and record success
453
00:17:48,760 --> 00:17:51,480
before you acknowledge completion to upstream systems.
454
00:17:51,480 --> 00:17:55,080
If a retry loop kicks in, your first step is to query your log for that key.
455
00:17:55,080 --> 00:17:56,920
If found and success are bought sent.
456
00:17:56,920 --> 00:17:59,320
If found and failed with a final code, surface the error.
457
00:17:59,320 --> 00:18:00,680
If not found, proceed.
458
00:18:00,680 --> 00:18:03,080
Finally, build for burst without tripping limits,
459
00:18:03,080 --> 00:18:05,800
queue outbound messages and process in controlled concurrency
460
00:18:05,800 --> 00:18:08,360
2 to 4 parallel sensor mailbox is sensible.
461
00:18:08,360 --> 00:18:09,960
Respect retry after globally.
462
00:18:09,960 --> 00:18:12,120
When you get throttled, slow the entire worker,
463
00:18:12,120 --> 00:18:13,400
not just one thread.
464
00:18:13,400 --> 00:18:15,160
Your goal isn't maximum throughput,
465
00:18:15,160 --> 00:18:17,720
it's guaranteed delivery without collateral damage.
466
00:18:17,720 --> 00:18:21,160
We've now turned send an email into a disciplined API call,
467
00:18:21,160 --> 00:18:24,280
scoped identity, clean payloads, predictable retries,
468
00:18:24,280 --> 00:18:25,480
and zero drama.
469
00:18:25,480 --> 00:18:28,040
Next, we wrap it in a power automate custom connector
470
00:18:28,040 --> 00:18:29,880
so every flow gets the same guardrails
471
00:18:29,880 --> 00:18:31,400
without reinventing anything.
472
00:18:31,400 --> 00:18:33,000
The implementation part three,
473
00:18:33,000 --> 00:18:35,080
power automate custom connector schema.
474
00:18:35,080 --> 00:18:36,440
Now we make this repeatable.
475
00:18:36,440 --> 00:18:38,040
A custom connector turns,
476
00:18:38,040 --> 00:18:41,400
careful HTTP into a single, governed action.
477
00:18:41,400 --> 00:18:44,040
Your flows can reuse without copy-pacing headers
478
00:18:44,040 --> 00:18:46,440
and retry logic like a teenager's homework.
479
00:18:46,440 --> 00:18:47,640
Start with basics.
480
00:18:47,640 --> 00:18:49,560
Base URL is RT-DPS-BARSH,
481
00:18:49,560 --> 00:18:53,400
graph-microsoft.com security is 002 with client credentials.
482
00:18:53,400 --> 00:18:55,400
No user delegation, no interactive login.
483
00:18:55,400 --> 00:18:57,480
Define the token URL as your tenants,
484
00:18:57,480 --> 00:18:59,320
Microsoft identity endpoint,
485
00:18:59,320 --> 00:19:01,400
and the scope as art default,
486
00:19:01,400 --> 00:19:03,240
which instructs graph to honor the app's
487
00:19:03,240 --> 00:19:05,400
granted application permissions, including mail,
488
00:19:05,400 --> 00:19:08,280
send, store the client secret or certificate reference
489
00:19:08,280 --> 00:19:09,880
in a secure connection parameter,
490
00:19:09,880 --> 00:19:12,760
ideally tied to an Azure key vault-backed connection,
491
00:19:12,760 --> 00:19:14,920
so creators can't exfiltrate credentials
492
00:19:14,920 --> 00:19:16,920
by helpfully previewing settings.
493
00:19:16,920 --> 00:19:21,400
Define one action, send mail, method, post, path, v1,
494
00:19:21,400 --> 00:19:23,080
user's more sender, send mail,
495
00:19:23,080 --> 00:19:25,880
create a required path parameter called sender,
496
00:19:25,880 --> 00:19:27,560
validated as an email address.
497
00:19:27,560 --> 00:19:28,680
Yes, validated.
498
00:19:28,680 --> 00:19:31,400
If the sender isn't in your application access policy group,
499
00:19:31,400 --> 00:19:34,440
the call will 403 and your error object will explain why.
500
00:19:34,440 --> 00:19:36,440
Don't guess, let exchange be the bouncer.
501
00:19:36,440 --> 00:19:38,920
Request schema should mirror graphs JSON.
502
00:19:38,920 --> 00:19:41,480
At minimum, subject, string, body,
503
00:19:41,480 --> 00:19:43,560
object with content type and content,
504
00:19:43,560 --> 00:19:45,880
two recipients, array of email addresses
505
00:19:45,880 --> 00:19:49,560
with optional cc recipients and bcc recipients arrays.
506
00:19:49,560 --> 00:19:51,320
Add attachments as an array of objects
507
00:19:51,320 --> 00:19:54,440
with name, content type, and content bytes basics for.
508
00:19:54,440 --> 00:19:58,120
Include a Boolean save to send items defaulting to true for HR.
509
00:19:58,120 --> 00:20:00,120
Add optional internet message headers
510
00:20:00,120 --> 00:20:02,120
as an array for lightweight tracing.
511
00:20:02,120 --> 00:20:03,560
Name and value pairs,
512
00:20:03,560 --> 00:20:05,960
Xtrace intent, Xbusiness id.
513
00:20:05,960 --> 00:20:07,360
Keep it small, mail gateways
514
00:20:07,360 --> 00:20:09,680
have opinions, security headers and correlation
515
00:20:09,680 --> 00:20:12,160
belong in the connector, not scattered in flows.
516
00:20:12,160 --> 00:20:14,160
Define a static header parameter,
517
00:20:14,160 --> 00:20:17,600
client request id that generates a guide per call when omitted,
518
00:20:17,600 --> 00:20:20,080
but allow override for advanced scenarios.
519
00:20:20,080 --> 00:20:21,840
Always return response headers,
520
00:20:21,840 --> 00:20:24,640
request id and date, expose them as outputs,
521
00:20:24,640 --> 00:20:26,560
so flows can lock them without spelunking
522
00:20:26,560 --> 00:20:28,480
into raw response metadata.
523
00:20:28,480 --> 00:20:31,280
Now error handling, standardize it.
524
00:20:31,280 --> 00:20:34,080
Define a normalized error object with fields.
525
00:20:34,080 --> 00:20:36,960
Status, int, code, string,
526
00:20:36,960 --> 00:20:41,440
message, string, request id, string, date, string,
527
00:20:41,440 --> 00:20:43,440
and retry after int.
528
00:20:43,440 --> 00:20:46,480
When graph returns 429, pass retry after and surface it
529
00:20:46,480 --> 00:20:48,320
for 5xx surface a retribal flag.
530
00:20:48,320 --> 00:20:51,600
For 401, return a clear acquire token failed code.
531
00:20:51,600 --> 00:20:55,600
For 403, policy denied with a message that explicitly says,
532
00:20:55,600 --> 00:20:58,480
sender not authorized by application access policy
533
00:20:58,480 --> 00:21:00,080
or permission missing.
534
00:21:00,080 --> 00:21:02,320
Save the detective work for actual mysteries.
535
00:21:02,320 --> 00:21:04,160
Connector policy's matter.
536
00:21:04,160 --> 00:21:06,000
Throttling, implement exponential back off
537
00:21:06,000 --> 00:21:08,720
with jitter inside the connector for 429 and 5x
538
00:21:08,720 --> 00:21:10,800
up to a safe cap, say three attempts.
539
00:21:10,800 --> 00:21:12,720
Respect retry after first timeouts,
540
00:21:12,720 --> 00:21:16,480
set a sensible HTTP timeout, e.g., 60 seconds,
541
00:21:16,480 --> 00:21:18,640
and allow the flow to configure a higher ceiling
542
00:21:18,640 --> 00:21:20,000
for large attachments.
543
00:21:20,000 --> 00:21:22,160
Concurrency, if your platform supports it,
544
00:21:22,160 --> 00:21:23,760
limit parallel calls per connection
545
00:21:23,760 --> 00:21:25,680
to avoid tripping tenant throttles.
546
00:21:25,680 --> 00:21:28,640
You're designing for steady throughput, not chaos.
547
00:21:28,640 --> 00:21:30,240
Outputs should be boring and useful.
548
00:21:30,240 --> 00:21:33,040
Return status request id, date, client request id,
549
00:21:33,040 --> 00:21:34,320
and a summary object.
550
00:21:34,320 --> 00:21:36,560
Recipients count, attachments count,
551
00:21:36,560 --> 00:21:37,760
save to send items.
552
00:21:37,760 --> 00:21:39,360
If you want to be generous, echo,
553
00:21:39,360 --> 00:21:41,040
the sender and the first to recipient
554
00:21:41,040 --> 00:21:42,800
for quick eyeballing in run history
555
00:21:42,800 --> 00:21:44,640
do not echo the entire body content.
556
00:21:44,640 --> 00:21:46,480
Your building traceability not a leak.
557
00:21:46,480 --> 00:21:47,600
In Power Automate flows,
558
00:21:47,600 --> 00:21:49,760
wrap the action in a scope named try.
559
00:21:49,760 --> 00:21:51,040
Follow with a scope named catch,
560
00:21:51,040 --> 00:21:52,560
configure to run on failure,
561
00:21:52,560 --> 00:21:53,920
and try call send mail.
562
00:21:53,920 --> 00:21:57,680
In catch, use results try to extract the connector's error object
563
00:21:57,680 --> 00:21:59,120
and log to a govern store,
564
00:21:59,120 --> 00:22:00,480
dataverse or SharePoint,
565
00:22:00,480 --> 00:22:01,760
capturing client request,
566
00:22:01,760 --> 00:22:05,440
each request id, date, sender, recipients, status, code,
567
00:22:05,440 --> 00:22:06,240
and message.
568
00:22:06,240 --> 00:22:06,960
Then branch.
569
00:22:06,960 --> 00:22:08,640
If code is policy denied,
570
00:22:08,640 --> 00:22:11,440
alert the admin owning application access policies.
571
00:22:11,440 --> 00:22:13,120
If code is acquired token failed,
572
00:22:13,120 --> 00:22:14,720
trigger a credential runbook.
573
00:22:14,720 --> 00:22:16,320
If retry after exists,
574
00:22:16,320 --> 00:22:18,560
recue or delay intelligently.
575
00:22:18,560 --> 00:22:20,480
Add a small guardrail before sending,
576
00:22:20,480 --> 00:22:22,720
validate sender against a maintained list mirrored
577
00:22:22,720 --> 00:22:24,640
from the HR transactional sender's group.
578
00:22:24,640 --> 00:22:26,560
It's a fast fail that saves a round trip
579
00:22:26,560 --> 00:22:28,160
and keeps noise out of your logs.
580
00:22:28,160 --> 00:22:30,240
And yes, use the safe navigation operator,
581
00:22:30,240 --> 00:22:33,120
the question mark in expressions when accessing optional outputs
582
00:22:33,120 --> 00:22:35,280
so a missing header never crashes your flow.
583
00:22:35,280 --> 00:22:38,160
What you get is a single action that encodes the rules.
584
00:22:38,160 --> 00:22:41,200
App-out, scope sender, durable retries,
585
00:22:41,200 --> 00:22:43,920
clean errors, and first class traceability.
586
00:22:43,920 --> 00:22:44,880
Congratulations.
587
00:22:44,880 --> 00:22:46,640
You've replaced folklore with an interface.
588
00:22:46,640 --> 00:22:49,920
Audit, monitoring, and incident prevention.
589
00:22:49,920 --> 00:22:51,120
You've secured delivery.
590
00:22:51,120 --> 00:22:51,840
Now prove it.
591
00:22:51,840 --> 00:22:54,720
Security that can't be measured is superstition in a hoodie.
592
00:22:54,720 --> 00:22:56,800
Start with audit sources that actually matter.
593
00:22:56,800 --> 00:22:59,040
Enter sign-ins for the app's service principle,
594
00:22:59,040 --> 00:23:01,360
tell you when tokens were minted and from where.
595
00:23:01,360 --> 00:23:03,680
Directory audit logs record permission changes,
596
00:23:03,680 --> 00:23:06,080
who granted mail, send, who consented,
597
00:23:06,080 --> 00:23:07,920
who touched the enterprise application.
598
00:23:07,920 --> 00:23:10,800
Exchange audit logs tell you the mailbox operations.
599
00:23:10,800 --> 00:23:13,760
Send A's, send on behalf, mailbox access.
600
00:23:13,760 --> 00:23:15,360
Per view holds the compliance trail.
601
00:23:15,360 --> 00:23:16,960
Route all three into log analytics
602
00:23:16,960 --> 00:23:18,800
or your CMV or diagnostic settings
603
00:23:18,800 --> 00:23:21,920
so you don't play download CSV forensics after an incident,
604
00:23:21,920 --> 00:23:23,840
centralized or enjoy chaos.
605
00:23:23,840 --> 00:23:24,880
Your choice.
606
00:23:24,880 --> 00:23:26,560
Tire together with correlation.
607
00:23:26,560 --> 00:23:29,600
Remember the tracing trinity client request ID you generate
608
00:23:29,600 --> 00:23:31,360
plus request ID and date from graph.
609
00:23:31,360 --> 00:23:33,440
Those IDs show up across systems.
610
00:23:33,440 --> 00:23:34,720
When an HR leader asks,
611
00:23:34,720 --> 00:23:38,240
did policy updates go to 8,000 employees at 9 a.m.?
612
00:23:38,240 --> 00:23:40,080
You pivot to your structured log store,
613
00:23:40,080 --> 00:23:43,200
a dataverse table or a SharePoint list if you're frugal.
614
00:23:43,200 --> 00:23:45,280
And query by client request ID.
615
00:23:45,280 --> 00:23:47,120
The entry links to enter sign-ins
616
00:23:47,120 --> 00:23:48,880
points to exchange audit for the mailbox
617
00:23:48,880 --> 00:23:51,360
and carries the graph request ID for cross checking.
618
00:23:51,360 --> 00:23:52,080
Mystery solved.
619
00:23:52,080 --> 00:23:53,440
No sayons required.
620
00:23:53,440 --> 00:23:55,600
Diagnostic settings aren't a suggestion.
621
00:23:55,600 --> 00:23:57,680
Turn them on for Microsoft Graph Sign-ins
622
00:23:57,680 --> 00:24:00,640
and directory audits via intras diagnostic export.
623
00:24:00,640 --> 00:24:02,720
Exchange admin and mailbox audit logs.
624
00:24:02,720 --> 00:24:06,160
Per view audit events if your compliance team expects attestations.
625
00:24:06,160 --> 00:24:08,160
Send them to log analytics with a retention
626
00:24:08,160 --> 00:24:10,000
that matches your regulatory window.
627
00:24:10,000 --> 00:24:12,560
Yes, 90 days is not enough if you answer to audits
628
00:24:12,560 --> 00:24:13,680
with multi-year look back.
629
00:24:13,680 --> 00:24:15,360
Storage is cheaper than litigation.
630
00:24:15,360 --> 00:24:17,280
Your operational logs need structure.
631
00:24:17,280 --> 00:24:19,600
In your flow sketch block, write a single record
632
00:24:19,600 --> 00:24:22,480
with fields that future you will actually use.
633
00:24:22,480 --> 00:24:25,440
Client request ID, request ID, date, sender,
634
00:24:25,440 --> 00:24:28,320
recipient's count, attachments count, status error code,
635
00:24:28,320 --> 00:24:31,200
redacted message, retry after seconds.
636
00:24:31,200 --> 00:24:34,400
Add intent tags, offer, policy update, termination,
637
00:24:34,400 --> 00:24:36,480
so you can pivot during incidents.
638
00:24:36,480 --> 00:24:38,720
Put a partition key on the business object ID
639
00:24:38,720 --> 00:24:40,960
if you ever want to answer, show me everything tied
640
00:24:40,960 --> 00:24:44,560
to candidate 1, 2, 3, 4, 5 without scanning the galaxy.
641
00:24:44,560 --> 00:24:47,200
Alerting is not email the admin when anything fails.
642
00:24:47,200 --> 00:24:49,680
That's how inboxes die, build focused signals.
643
00:24:50,800 --> 00:24:53,040
Failure rate threshold, alert when more than
644
00:24:53,040 --> 00:24:55,280
x% of sense fail in 10 minutes.
645
00:24:55,280 --> 00:24:58,080
Threatlings bike, alert on clusters of photo 29
646
00:24:58,080 --> 00:25:00,000
with retry after above a threshold.
647
00:25:00,000 --> 00:25:02,080
Your flow is outrunning your tenant's patience.
648
00:25:02,080 --> 00:25:04,160
Policy denied detection, alert immediately.
649
00:25:04,160 --> 00:25:06,000
Someone tried to send from a mailbox outside
650
00:25:06,000 --> 00:25:08,160
the application access policy scope.
651
00:25:08,160 --> 00:25:11,200
That's either misconfiguration or an attempted escalation.
652
00:25:11,200 --> 00:25:13,520
A normalist volume.
653
00:25:13,520 --> 00:25:15,840
Detect HR blast patterns at odd hours
654
00:25:15,840 --> 00:25:17,680
or unusual recipient domains,
655
00:25:17,680 --> 00:25:19,840
correlate with tarryl and outbound spam policies
656
00:25:19,840 --> 00:25:21,760
so you don't trip compliance landmines.
657
00:25:21,760 --> 00:25:24,800
Yes, tenant restrictions and outbound policies still apply.
658
00:25:24,800 --> 00:25:26,400
The external recipient rate limits
659
00:25:26,400 --> 00:25:28,320
and bulk sender authentication requirements
660
00:25:28,320 --> 00:25:30,160
exist to protect you and everyone else.
661
00:25:30,160 --> 00:25:33,120
Respect them, set dashboards for daily external recipient counts
662
00:25:33,120 --> 00:25:35,360
and SPF or DKM demarc alignment
663
00:25:35,360 --> 00:25:38,160
or enjoy bizarre deliverability issues you'll blame on graph.
664
00:25:38,160 --> 00:25:40,800
Now the runbook, the thing you wish you had
665
00:25:40,800 --> 00:25:42,720
during the incident you deny will happen.
666
00:25:42,720 --> 00:25:44,800
Include, revoke and rotate,
667
00:25:44,800 --> 00:25:48,560
immediate steps to disable the app's client secret or certificate
668
00:25:48,560 --> 00:25:51,920
with key vault integration and who's authorised to do it.
669
00:25:51,920 --> 00:25:56,160
Policy lockdown, how to disable the application access policy temporarily
670
00:25:56,160 --> 00:25:59,200
or better, how to tighten the scope by removing mailboxes
671
00:25:59,200 --> 00:26:01,200
from the HR transactional sender's group.
672
00:26:01,200 --> 00:26:04,880
Validation tests, exact test application access policy commands
673
00:26:04,880 --> 00:26:07,120
to confirm state before and after changes.
674
00:26:07,120 --> 00:26:09,440
Regression suite, token acquisition test,
675
00:26:09,440 --> 00:26:11,360
a controlled send to a test mailbox
676
00:26:11,360 --> 00:26:13,840
with xtrace intent, HR policy check
677
00:26:13,840 --> 00:26:17,280
and verification steps in exchange audit and log analytics.
678
00:26:17,280 --> 00:26:20,480
Rollback, how to restore prior config from documented diffs
679
00:26:20,480 --> 00:26:21,840
not memory.
680
00:26:21,840 --> 00:26:24,400
Close the loop we planted earlier, the one misstep
681
00:26:24,400 --> 00:26:27,200
that silently exposes every mailbox is skipping
682
00:26:27,200 --> 00:26:29,600
the application access policy because mail
683
00:26:29,600 --> 00:26:31,440
send is already least privileged.
684
00:26:31,440 --> 00:26:34,160
It isn't, without the policy your app can impersonate
685
00:26:34,160 --> 00:26:35,840
the tenant's entire directory.
686
00:26:35,840 --> 00:26:38,080
The fence is what prevents lateral blast radius.
687
00:26:38,080 --> 00:26:40,960
It also gives you a crisp error, 403 policy denied
688
00:26:40,960 --> 00:26:43,040
when someone wonders outside the lane.
689
00:26:43,040 --> 00:26:45,040
Incidents become containable by design.
690
00:26:45,040 --> 00:26:47,760
Last piece, ownership, assign a human owner for the app,
691
00:26:47,760 --> 00:26:50,560
the policy and the connector, document contact methods
692
00:26:50,560 --> 00:26:52,960
in the enterprise application's directory metadata.
693
00:26:52,960 --> 00:26:56,080
When the alert files are 214 AM, the on-call engineer
694
00:26:56,080 --> 00:26:58,640
shouldn't be spelunking SharePoint to find a name.
695
00:26:58,640 --> 00:27:00,240
Efficiency is not an accident,
696
00:27:00,240 --> 00:27:02,800
it's documentation plus accountability.
697
00:27:02,800 --> 00:27:05,440
You've now moved from, I hope it works too,
698
00:27:05,440 --> 00:27:08,480
I can prove it, detect drift and recover fast.
699
00:27:08,480 --> 00:27:11,040
That's incident prevention, not incident theater.
700
00:27:11,040 --> 00:27:13,280
Here's the point, stop impersonating people
701
00:27:13,280 --> 00:27:14,560
with brittle service accounts.
702
00:27:14,560 --> 00:27:16,080
Use an app registration with mail,
703
00:27:16,080 --> 00:27:18,400
send, fence it with an application access policy,
704
00:27:18,400 --> 00:27:20,960
send through graph and log like an adult.
705
00:27:20,960 --> 00:27:24,320
If this saved you from another midnight outage, subscribe.
706
00:27:24,320 --> 00:27:26,160
Next, grab the exact power shell,
707
00:27:26,160 --> 00:27:28,800
graph payload schemers and the custom connector definition.
708
00:27:28,800 --> 00:27:31,600
I'm walking through each command and endpoint in the follow-up.
709
00:27:31,600 --> 00:27:33,600
Implemented before your next HR cycle,
710
00:27:33,600 --> 00:27:35,360
so your offer send means delivered,
711
00:27:35,360 --> 00:27:36,960
auditable and scoped.
712
00:27:36,960 --> 00:27:39,040
Entropy wins by default, choose structure.