← Back to blog
AWS Daily with Divine

Your Secrets Manager Bill Has Email Addresses In It. Look Here First.

5 min read
awssecrets-managerparameter-storecost-optimizationiamsecurity

I was looking at our AWS bill recently and noticed Secrets Manager was charging more than expected. Looked closely. Found this stored as a secret:

DEFAULT_FROM_EMAIL = "alerts@myapp.com"
NOREPLY_EMAIL      = "noreply@myapp.com"

Email addresses. Stored as secrets. For a service that sends emails almost every second.

Every send was triggering a GetSecretValue call, which triggers a KMS decrypt, which is another billed API call. The bill reflected that.

This is the most common pattern I see at fintechs and platforms moving fast. Teams default to Secrets Manager for every config value because it sounds more secure. Then they pay for it every day, on values that didn't need security at all.

The actual cost difference

ServicePer-entry costAPI callsEncryption
Parameter Store (Standard)Free up to 10,000 entriesFreeOptional
Parameter Store (Advanced)$0.05 / entry / month$0.05 / 10K callsRequired
Secrets Manager$0.40 / secret / month$0.05 / 10K calls + KMS decryptRequired, always-on

That's roughly a 40x markup per entry when you store config in Secrets Manager that could live in the free Parameter Store tier.

Multiply by every config value across every service. The footprint adds up silently.

When Parameter Store is the right call

Use Parameter Store for anything that isn't a true secret:

  • Service URLs (https://api.partner.com)
  • Feature flags (ENABLE_NEW_CHECKOUT=true)
  • Environment-specific config (LOG_LEVEL=info)
  • Email addresses, support contacts, usernames
  • Non-sensitive identifiers (account IDs, region names, queue names)
  • Public configuration that ends up in client-side bundles anyway

The default tier is free up to 10,000 entries. Plain text storage. You can opt in to KMS encryption per parameter when you want it. No automatic rotation, but most config doesn't need rotation.

When Secrets Manager actually earns its cost

Use Secrets Manager for credentials where rotation or strict access control is non-negotiable:

  • Database passwords, especially with automatic rotation to RDS, Redshift, or DocumentDB
  • Third-party API keys that should rotate on a schedule
  • OAuth client secrets, JWT signing keys
  • Payment processor credentials, anything that would cost real money if leaked
  • Cross-account credentials where Secrets Manager's resource policies do meaningful work

The $0.40/month per secret is cheap insurance for things that genuinely need to rotate without code changes.

The IAM angle most teams miss

Even with the right service chosen, the IAM permissions are where the second wave of risk hides.

Wildcard policies like:

{
  "Effect": "Allow",
  "Action": "secretsmanager:GetSecretValue",
  "Resource": "*"
}

Mean every role that touches Secrets Manager can read every secret. Audit-fail in any serious compliance environment, and a leaked role becomes a credential mass-extraction event.

Tighten to resource-scoped policies:

{
  "Effect": "Allow",
  "Action": "secretsmanager:GetSecretValue",
  "Resource": [
    "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/db-*",
    "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/payment-api-*"
  ]
}

Same applies to Parameter Store. Scope to path prefixes:

{
  "Effect": "Allow",
  "Action": "ssm:GetParameter",
  "Resource": "arn:aws:ssm:us-east-1:123456789012:parameter/prod/config/*"
}

If you're using IRSA on EKS, scope these to the workload's service account role, not the node role.

The KMS double-charge nobody mentions

Secrets Manager always uses KMS for encryption. Every GetSecretValue call incurs:

  1. The Secrets Manager API charge
  2. A KMS Decrypt API call against the encrypting key (AWS-managed or customer-managed)

KMS Decrypt is $0.03 per 10,000 requests. Cheap, except when a service calls GetSecretValue on every request because someone wired it into the hot path instead of caching at boot.

I've seen workloads do 50 million decrypt calls per month. That's $150/month for the KMS portion alone, on top of the Secrets Manager bill, on top of the time engineers spent debugging mysterious latency because each call adds ~10-30ms.

Always cache secrets at process start. Refresh on a schedule (every 5-15 minutes is fine) or on explicit signal. Never call GetSecretValue per-request unless you're rotating mid-flight, which you almost certainly aren't.

How to audit yours today

In every account, run:

aws secretsmanager list-secrets \
  --query 'SecretList[*].[Name,Description]' \
  --output table

Scan the names. Anything that looks like config (URLs, emails, flags, region names, non-sensitive identifiers) is a candidate to move.

For Parameter Store, check what's already there:

aws ssm describe-parameters \
  --query 'Parameters[*].[Name,Type,Tier]' \
  --output table

Look for entries on the Advanced tier that don't need the larger value size or rotation features. Those are paying $0.05/month each for capabilities they aren't using.

Migration path

Moving a value from Secrets Manager to Parameter Store is mechanical, but order matters:

  1. Read the existing secret
  2. Write to Parameter Store with a sensible path
  3. Update the application to read from the new location on the next deploy
  4. Verify via CloudTrail that no calls are still hitting the old secret
  5. Delete the secret with the recovery window set, so you can roll back if needed
SECRET_VALUE=$(aws secretsmanager get-secret-value \
  --secret-id alerts-email \
  --query SecretString --output text)
 
aws ssm put-parameter \
  --name "/prod/config/alerts-email" \
  --value "$SECRET_VALUE" \
  --type String \
  --description "Alerts from-address. Non-sensitive."
 
aws ssm get-parameter --name "/prod/config/alerts-email"
 
aws secretsmanager delete-secret \
  --secret-id alerts-email \
  --recovery-window-in-days 30

Always use the recovery window. --force-delete-without-recovery is a gun pointing at your foot.

Cost recap

Value typeRight serviceApprox cost
Service URLs, flags, emails, configParameter Store (Standard)$0
Large config values (>4KB)Parameter Store (Advanced)$0.05/mo per entry
Database passwords with rotationSecrets Manager$0.40/mo per secret
API keys, third-party credentialsSecrets Manager$0.40/mo per secret
Cross-account credentialsSecrets Manager$0.40/mo per secret

The default for new config values should be Parameter Store Standard. You should be able to explain why every Secrets Manager entry is there. If you can't, it doesn't belong there.


Have you audited what's in your Secrets Manager today? You'd be surprised how many email addresses live in there.

Originally shared on LinkedIn.