CAN-SPAM §5 defines transactional email (password resets, receipts, security alerts) as exempt from its commercial email requirements — but that exemption disappears the moment promotional content is added to a transactional template. Injecting a 'You might also like' banner into an order confirmation reclassifies the entire message as commercial, requiring unsubscribe compliance that transactional flows typically lack. Beyond compliance, mixing the two in a single code path destroys the deliverability benefit of keeping transactional mail on dedicated IP streams: a complaint on a promotional email poisons deliverability for password resets on the same IP pool.
Info because the impact is primarily deliverability degradation and potential regulatory reclassification rather than a direct violation — the severity escalates if promotional content is added to transactional templates.
Maintain separate functions and separate sending configurations for transactional and marketing email — never route both through the same code path.
// lib/email.ts — explicit type separation
export async function sendTransactionalEmail(opts: { to: string; subject: string; html: string }) {
// No List-Unsubscribe headers; no promotional content in html
return client.send({ from: 'noreply@yourproduct.com', ...opts })
}
export async function sendMarketingEmail(opts: { to: string; subject: string; html: string; unsubUrl: string }) {
// Always includes unsubscribe headers; only called for opted-in subscribers
return client.send({
from: 'hello@yourproduct.com',
headers: { 'List-Unsubscribe': `<${opts.unsubUrl}>`, 'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click' },
...opts,
})
}
If using Postmark, configure separate message streams (Transactional vs. Broadcast). This also gives you separate deliverability metrics to catch issues before they affect your transactional sending reputation.
ID: email-sms-compliance.content-delivery.transactional-vs-marketing
Severity: info
What to look for: Enumerate every relevant item. CAN-SPAM and other regulations apply only to "commercial" (marketing/promotional) email — not to "transactional" email (messages that facilitate a transaction the recipient has requested, such as receipts, password resets, account security alerts, shipping confirmations). Mixing promotional content into transactional emails can cause a transactional email to be reclassified as commercial by regulators, subjecting it to CAN-SPAM opt-out requirements. Check whether the codebase distinguishes these types. Are marketing emails sent through a different path or template system than transactional emails? Does the email service configuration use different IP pools or sender streams for transactional vs. marketing? Look for clear naming conventions: sendTransactional() vs. sendMarketing(), or separate email template directories (emails/transactional/ vs. emails/marketing/).
Pass criteria: At least 1 of the following conditions is met. Transactional and marketing emails are clearly distinguished in code, sent via separate paths or template systems, and no promotional content is injected into transactional email templates.
Fail criteria: All email is sent through the same function with no type distinction. Transactional email templates include promotional banners or "check out our latest offer" sections. No naming or structural distinction between email types in the codebase.
Skip (N/A) when: Application sends only one type of email (only transactional or only marketing).
Detail on fail: Example: "Order confirmation email template includes a 'You might also like' promotional section. Adding marketing content to transactional email may cause it to be reclassified as commercial, requiring unsubscribe compliance.".
Remediation: Maintain separate send paths and keep transactional emails clean:
// lib/email.ts — separate functions for each email type
export async function sendTransactionalEmail(opts: TransactionalEmailOptions) {
// No unsubscribe headers required; no marketing content in templates
return client.send({
from: `YourProduct <noreply@yourproduct.com>`,
to: opts.to,
subject: opts.subject,
html: opts.html, // template must NOT include promotional content
})
}
export async function sendMarketingEmail(opts: MarketingEmailOptions) {
// Always include List-Unsubscribe headers and unsubscribe link
// Only send to confirmed, opted-in subscribers
return client.send({
from: `YourProduct <hello@yourproduct.com>`,
headers: {
'List-Unsubscribe': `<${opts.unsubscribeUrl}>`,
'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click',
},
to: opts.to,
subject: opts.subject,
html: opts.html,
})
}
If using Postmark, configure separate message streams for transactional and broadcast (marketing) email — this also gives you separate deliverability metrics.