SOC 2 Audit Trails for Transactional Email
Auditors treat transactional email as material — and CloudTrail isn't enough. The change-control evidence SES needs for a clean SOC 2 review.
A startup CTO I know spent the Saturday before her SOC 2 Type II audit reconstructing the change history of three transactional email templates from CloudTrail logs and Slack messages. The auditor had asked, reasonably, for evidence of change control over customer-facing communications. The CTO had a Git repo for the templates and CloudTrail enabled. What she didn't have was a way to show "on this date, this template was changed from X to Y by this person, with this approval, for this reason."
She reconstructed it. The audit passed. The next week, she made the change-control gap her engineering team's top quarterly priority.
This is increasingly common. Auditors used to treat email templates as marketing collateral. They don't anymore — at least not when the templates are password resets, security notifications, billing communications, or invitations carrying access tokens. Those are functional parts of the system, and SOC 2 controls like CC8.1 (change management) and CC7.2 (system monitoring) reach into them.
Here's what good audit evidence looks like for SES, and how to build it before you need it.
What auditors actually look for in email change control
The SOC 2 Common Criteria don't mention email. They talk about changes to "system components" and "system configurations" and require evidence that those changes are authorized, tested, documented, and traceable.
Transactional email templates fall into this scope when they meet one or more of these conditions:
- They carry security-sensitive content (password resets, MFA codes, login alerts).
- They carry financial information (invoices, billing notices, plan changes).
- They are part of an authentication or authorization flow (invitations, magic links, account confirmations).
- They are part of a regulated workflow (HIPAA-touching content, regulated industry notices).
When templates meet these conditions, an auditor will reasonably ask for the same evidence they'd ask about a code change: who made the change, who approved it, when, why, and what changed. The fact that the change happened in the AWS Console rather than a CI pipeline doesn't make it less material.
The evidence package that satisfies an auditor has these elements:
- A list of all changes to in-scope templates during the audit period.
- For each change, the author, approver, timestamp, and a description of what changed.
- For each change, a link to the ticket, PR, or change request that authorized it.
- Evidence that the change went through your documented change management process.
- Evidence that production access is restricted to authorized principals.
- Retention of the evidence for at least the audit period plus typical lookback (12 months minimum, usually).
If you can't assemble that for any in-scope template change in the last 12 months, you have a finding.
CloudTrail's coverage and its gaps
CloudTrail is the obvious starting point. It captures every SES API call, including UpdateTemplate and UpdateEmailTemplate. The event includes the principal, the timestamp, the source IP, the API parameters, and the response.
This is necessary but not sufficient. The gaps are specific:
Content is captured opaquely. The CloudTrail event for UpdateTemplate includes the request payload, which includes the template body. But CloudTrail records what was sent, not the diff against what was there before. To produce a diff, you need to correlate two CloudTrail events — the previous update and the current one — and compute the diff yourself. For HTML templates, that diff is unreadable without tooling.
Principal is not human. The event records the IAM principal that made the call. If your CI role published the template, that's the role you see — not the human whose PR triggered the CI run. Mapping principals back to humans requires correlating CloudTrail with your CI logs, your Git history, and your authentication system.
Reason is missing. CloudTrail does not capture why a change was made. You'll need that link from somewhere else — a Jira ticket, a PR description, a Slack message — and you'll need to correlate it manually.
Retention requires effort. CloudTrail's default retention in the AWS Console is 90 days. For audit evidence over a 12-month window, you need CloudTrail Lake or events delivered to S3 with a lifecycle policy. Many teams don't have this configured deliberately.
Console events are spotty for older APIs. Some SES Console actions trigger API calls that CloudTrail records well; some trigger sequences that are harder to reconstruct. The v2 API is generally cleaner here than v1.
The summary: CloudTrail tells you that a principal called the API at a time. It does not, on its own, tell you who, what, or why in a form an auditor will accept without follow-up questions.
The "who, what, when, why" minimum
The minimum viable audit record for a template change has four fields. Any system that produces these — whether built or bought — is on the right track.
Who. The human, not the principal. "Maria Rodriguez via the ses-deploy CI role" rather than "ses-deploy". This requires correlating the IAM principal with the upstream actor — typically the Git commit author or the workflow approver.
What. The actual content delta. Not "the template was updated" but "subject changed from 'Welcome to Acme' to 'Welcome to Acme Pro'; body added the variable {{plan_name}} on line 47; removed the old marketing footer." A textual diff, viewable a year later.
When. Timestamps for both the change being authored (when the PR was opened, when the draft was created) and the change being applied (when the production publish happened). These can differ by hours or days; both matter.
Why. A pointer to the authorization. A linked ticket, a linked incident, a linked PR description. "Fix typo in welcome subject as reported in JIRA-1234" is enough. "Hot fix" is not.
The discipline this implies is that template changes always carry a reason field, the reason links to a system of record outside the SES API, and the link is preserved alongside the audit record.
Linking template changes to tickets and code changes
The cleanest way to get linkable reasons is to make the publishing surface require them.
If you're using Pattern A from the versioning post — Git as source of truth — your PRs already carry descriptions, and your commit messages should reference tickets. Convention: every PR that touches a template includes a ticket reference. Every CI run that publishes a template logs the commit SHA and PR number it came from. The audit chain is then: CloudTrail event → CI run → commit SHA → PR → ticket.
If you're using IaC (Terraform, CDK), the same pattern applies but routed through your IaC plan. Plan output captures the diff. The plan being applied is the auditable event. The chain is: CloudTrail event → IaC apply → plan SHA → commit → PR → ticket.
If you're using a control layer like Sovy, the control layer captures who, what, when, and why in one place — every change carries an author, an optional ticket link, and a content diff, and the audit log is exportable in the form an auditor wants. The chain shortens: control layer log → ticket. CloudTrail is still recorded for the underlying API call, but the primary evidence is the control layer's own audit log, which captures the human-facing facts directly.
The point isn't to eliminate CloudTrail. CloudTrail is a defense-in-depth log that proves what API calls actually happened, which catches "the control layer says X but SES received Y" inconsistencies. The point is that CloudTrail alone makes assembling audit evidence into archaeology, and archaeology is expensive at audit time.
Retention, immutability, and export
SOC 2 doesn't prescribe retention periods, but in practice your auditor will ask for the audit period plus enough lookback to verify controls were operating consistently. 12 months is a common floor. 24 months is safer.
The retention rules:
Retain for at least 12 months. S3 with a 12-month lifecycle is the cheapest way for CloudTrail. CloudTrail Lake is more expensive but gives you query capability.
Make it immutable. The audit log can't be the kind of system the engineering team can edit. S3 Object Lock, CloudTrail's tamper-resistant log file integrity validation, or a SaaS audit log with no admin-side delete are all viable.
Make it exportable. When the auditor asks for "all template changes in Q3 2025," you need to produce a CSV or JSON export, not screenshots. Build the export path before audit week.
Test the retrieval. Once a quarter, pretend an auditor asked for evidence for a specific change. Walk the retrieval. If you can't produce who, what, when, and why for an arbitrary template change made nine months ago in under thirty minutes, your audit posture is weaker than you think.
A sample evidence package
For a SOC 2 Type II audit covering January through December, the evidence package for SES change control might include:
- A document describing the change management process for transactional email templates, including who is authorized to publish to production and what approvals are required.
- The IAM policies and SCPs that enforce that authorization in AWS.
- A CSV export of every template change during the period, with columns: timestamp, template name, environment, author (human), reviewer/approver, ticket link, content diff summary.
- For a sampled set of changes (auditors typically pick 5–10 at random), the underlying ticket, the PR or change request, the CloudTrail event for the API call, and any test/preview evidence.
- Evidence that any out-of-process changes (incidents, hotfixes) were retroactively documented and approved.
- A retention statement describing where the evidence is stored, for how long, and how it's protected from tampering.
If your team already keeps PRs, tickets, and CloudTrail logs, the elements exist — they just aren't pre-correlated. Pre-correlating them once a year, by hand, on the eve of audit, is a recipe for the Saturday-before-audit story I opened with. Pre-correlating them continuously is the goal.
Common findings and how to avoid them
A short list of findings I see repeatedly:
No mapping from IAM principal to human. Fix: every CI publish records the upstream commit SHA and the PR author. Tag the audit log entry with both.
Hot-fix Console edits with no ticket. Fix: documented incident-time process that requires retroactive ticket creation within 24 hours. Audit logs flagged as "out-of-process" pending the ticket reference.
No content diff stored. Fix: store the previous and new versions of the template in your audit system on every publish. Generate a diff on demand.
Production access not restricted. Fix: SCPs that deny UpdateEmailTemplate to any principal except the publishing role and a small list of break-glass admins. Documented break-glass procedure with retroactive review.
No retention beyond 90 days. Fix: S3 + Object Lock + lifecycle, or CloudTrail Lake, or a control layer with retention guarantees.
Templates outside the inventory. Fix: a quarterly reconciliation that lists every template in production SES and confirms each one is owned, in scope or out of scope, and has audit history. Templates without owners get owners or get deleted.
The reframe for engineering teams
Most engineering teams don't think about SOC 2 evidence when they're building. They think about it three weeks before the audit, panic, and then build it in a hurry.
The cheaper path is to treat audit evidence as a side effect of your normal change management. PRs already record who and what. Tickets already record why. CloudTrail already records when the API call happened. The work is correlating these continuously into a queryable log, not generating new data.
The team I mentioned at the start of this post now has every transactional template change recorded with the four fields, exportable on demand, retained for two years. The engineering investment was about three weeks. The next audit took half a day instead of a Saturday.
Whether you build that correlation yourself or buy it as part of a control layer, the structural answer is the same: stop treating SES templates as a thing the auditor won't ask about. They will. Often.
Sovy is a control layer for Amazon SES templates. It captures who, what, when, and why for every template change, with content-level diffs and exportable audit logs that satisfy SOC 2 evidence requirements out of the box. If your audit prep involves correlating CloudTrail with Slack screenshots, we'd like to hear from you.