If your product lets people reply to a notification and have it appear in a ticket, or forward a document to an address to import it, you have an unauthenticated API whose transport is email.
Anyone can send mail claiming to be anyone. The From header is a string the sender chooses, and it is what most implementations key on.
This post is what to check in an inbound email path. For whoever built the reply-by-email feature, which is usually the person who built the notification it replies to.
The From header is not authentication
It is a claim. Treating it as identity means anybody who can send mail can act as any of your users: post to their ticket, attach a document to their account, reply on their behalf in a thread their colleagues read.
What actually provides some assurance is authentication of the sending domain, via SPF and DKIM, with DMARC alignment to the domain in the From header. Your mail provider usually evaluates these and passes the results to you. Read them, and reject or quarantine anything that fails alignment.
That establishes the mail came from somewhere authorised to send for that domain. It does not establish which person sent it, and for a shared mailbox it cannot.
Put the authorisation in the address
The mechanism that works is a per-conversation secret in the recipient address:
reply+t_9f3a1c7b2e5d4f60@mail.example.com
The token is unguessable, scoped to one conversation, and expires. Compare with the common weak version:
ticket-4821@mail.example.com # sequential, enumerable, forever
With the second, anyone can post to any ticket by counting. With the first, an attacker needs a token that was only ever sent to the participants.
Two details. The token should authorise the conversation, not the person, and your interface should show who the message claimed to be from and whether the domain authenticated, so a human can judge. And expire tokens with the conversation, or a two-year-old notification remains a working write credential.
Attachments are file uploads from an unauthenticated source
Every concern you have about your upload endpoint applies here, minus the authentication, and usually none of the controls came along.
Size caps, count caps, content type determined from the bytes rather than the declared type or the filename, filenames sanitised rather than used as paths, and storage somewhere that never serves them inline from your own origin. If your product renders an attached image in a ticket, you are serving attacker-supplied content to your users.
Archives deserve specific attention: an expanded archive can be enormously larger than the file, and entries can contain path traversal. If you expand them, bound the expansion and reject entries with absolute or traversing paths.
HTML email is untrusted markup you will render
Mail arrives as HTML. Storing it and later rendering it in your web interface is stored cross-site scripting unless you sanitise it properly, and email HTML is unusually hostile to sanitise because it legitimately contains inline styles, tables, and mangled markup from a dozen clients.
Sanitise on output with a strict allowlist, strip scripts, event handlers and external resource loads, and prefer rendering the plain text part where you have one. If you must render HTML, do it in a sandboxed iframe with a restrictive policy rather than inline in your application's document.
The loops
Two failure modes that are not attacks and cause real incidents.
Autoresponders. You send a notification, an out-of-office replies, your system treats it as a message and notifies, which triggers the autoresponder again. Detect the standard headers that mark automatic messages and drop them, and apply a rate limit per conversation regardless.
Your own address. If your system can receive its own notifications, one misconfiguration produces an unbounded loop. Refuse mail from your own sending addresses.
Rate limit, and be careful what you key on
Limit per sender address, per conversation, and per source, and remember that the sender address is spoofable, so it is a convenience key rather than a control. The connecting server is the more reliable signal, and your provider gives it to you.
Cap the message size before you process it, not after, and reject rather than truncate, because a truncated MIME structure parses in unpredictable ways.
Check yours
# 1. Send a message with a forged From claiming to be a real user of a
# conversation you have a token for. Does the product attribute it to them?
# 2. Send to a guessed reply address (increment an id). Does it land?
# 3. Attach a file with a double extension and a mismatched content type.
# What does the product store, and what does it serve it as?
# 4. Send HTML containing a script tag and an external image.
# Open the ticket and read the rendered source.
# 5. Reply to a conversation closed a year ago. Does the address still work?
Test two is the one that finds the serious problems, and it takes a minute.
The concession
Every control here adds friction to a feature that exists because replying from your inbox is genuinely easier than logging in, and that convenience is why customers like it. Rejecting mail that fails domain alignment will occasionally reject a legitimate message from a customer whose own mail configuration is imperfect, and there are a lot of those.
The workable position is to quarantine rather than silently drop, surface the authentication result in the interface so a human can decide, and keep the hard control where it belongs: the unguessable token in the address. That one does not depend on anyone else's DNS being correct.
The implication
The reply-by-email path is usually built by someone thinking about convenience, and it lands in the same database as everything your authenticated API writes.
Send yourself a forged message to your own product. Whether it appears under someone else's name is the whole answer.