跳到正文
DEV Community· live-direct-marketing·· 6 小時前AI 評分50

為何已送達的電郵仍會進入垃圾郵件資料夾,以及如何測試收件匣投遞位置

Why Delivered Emails Land in Spam and How to Test Inbox Placement

AI 導讀

發送服務接受請求或收件伺服器接受郵件,都不能證明郵件已進入收件匣;實際位置需由收件郵箱觀察確認。文中提供 Python 標準庫腳本,從本機 .eml 檔案擷取標頭,但腳本不會驗證身份驗證結果,也不能判斷郵件所在資料夾。測試報告應列出分母、未確認結果及各收件服務的情況;受控郵箱的結果只反映受測郵箱、郵件、發送路徑和觀察時段,不能代表所有用戶。

正文

Your application sends a password reset. The email API returns a message ID. A delivery event arrives. The user still cannot find the message.

Then someone checks the spam folder.

This is where email debugging becomes confusing: several different systems have reported success, but none of those signals necessarily describes the folder the user sees. Amazon SES, for example, distinguishes acceptance of a sending request from delivery to a recipient's mail server. Neither event is an inbox placement report. 12

To debug the gap, you need evidence from the receiving mailbox. This guide explains what to collect, how to inspect a saved message with Python, and how to run an inbox placement test without turning a small sample into an unsupported promise.

What inbox placement actually measures

For this workflow, an inbox placement test means sending a message to controlled recipient mailboxes and recording where each received copy appears. These test accounts are often called seed mailboxes.

Keep four questions separate:

Did the sending provider accept the request? An API response or submission log can answer this.

Did the recipient's mail server accept the message? A documented delivery event, or that server's final positive SMTP response after message transfer, can provide evidence. A successful response to RCPT TO alone is not enough. 12

Where did the message appear? This requires an observation from the recipient mailbox or a system with access to its folder state.

Did the recipient do anything? A reply or completed application action answers a different question again.

For a concrete example, SES returns a MessageId when it accepts a message. Its API documentation also describes cases where an accepted message is never sent. Separately, an SES delivery event records delivery to the recipient's mail server and includes that server's SMTP response. 12

That distinction changes what you investigate. If the request was rejected, examine the application and sending service. If the recipient server accepted the email but a controlled mailbox shows it in spam, inspect the received copy and the sending conditions.

A successful event at one stage does not establish the outcome at the next stage.

Start with the received message

Save the original received message as an .eml file. Use the copy from the destination mailbox, rather than a screenshot of the email or the HTML your application submitted. The received copy lets you inspect the authentication results added along the delivery path.

Here is a deliberately simplified, synthetic header example:

From: Example App team@example.com
Return-Path: bounce@mailer.example.com
Message-ID: placement-example-001@example.com
Authentication-Results: mx.receiver.example.net;
spf=pass smtp.mailfrom=mailer.example.com;
dkim=pass header.d=example.com;
dmarc=pass header.from=example.com

The addresses and results are illustrative. They are not evidence of a real delivery test.

The visible From domain is example.com. The reported SPF identity uses mailer.example.com, while the reported DKIM signing domain is example.com. Under relaxed alignment, the SPF domains in this example align; the DKIM domain also matches the visible From domain. DMARC can pass when at least one method both passes authentication and aligns with that domain. Strict alignment requires an exact domain match, so the illustrated SPF path would not satisfy strict alignment. 4

The diagram shows relaxed alignment. Passing authentication does not guarantee inbox placement.

There is an important trust boundary here. An Authentication-Results field is a report, not independent proof. RFC 8601 warns that the presence of the field does not establish that its contents are valid. Identify the results added by the receiving system you trust; do not automatically trust every field with that name or assume its position alone establishes trust. 3

Also keep authentication and placement as separate observations. A receiver reporting dmarc=pass has reported an authentication outcome. It has not promised to place the message in the inbox. 10

Inspect the headers with a small Python script

Python's standard library can parse a saved message without a third-party package. The following script prints a few useful headers as JSON. It preserves repeated fields with get_all(), which matters when a message contains more than one Authentication-Results field. 56

Save this as inspect_email_headers.py:

"""Print selected headers from a local .eml file without network access."""

import argparse
import json
from email import policy
from email.parser import BytesParser
from pathlib import Path

def main():
parser = argparse.ArgumentParser(
description="Inspect email headers; does not verify authentication."
)
parser.add_argument("eml", type=Path, help="Path to a saved .eml message")
args = parser.parse_args()

try:
    with args.eml.open("rb") as source:
        message = BytesParser(policy=policy.default).parse(
            source, headersonly=True
        )
except OSError as error:
    parser.error(str(error))

names = (
    "From",
    "Return-Path",
    "Message-ID",
    "Authentication-Results",
)
report = {
    name: [str(value) for value in message.get_all(name, [])]
    for name in names
}
print(json.dumps(report, indent=2, ensure_ascii=True))

if name == "main":
main()

Run it against a message saved on your machine:

python3 inspect_email_headers.py received-message.eml

Each output value is a list. A missing header produces an empty list; repeated headers remain separate entries. This makes it easier to compare two received copies without accidentally dropping one authentication report.

The script extracts evidence; it does not validate it. It performs no DNS lookup, does not verify a DKIM signature, does not establish whether an authentication report is trustworthy, and cannot determine the folder from these headers. Record the folder separately when you collect the message.

For example, if the receiver reports a different DKIM signing domain after you switch sending providers, that is a useful lead. Investigate whether the new provider is signing with the intended domain. Do not turn a changed header into an automatic explanation for every placement difference.

Run a placement test that resembles the real send

A manually composed message from your personal mailbox does not test the route used by your application. For a useful comparison, send through the same application path, provider configuration, sender identity, and template pipeline that you want to investigate.

Use test users and non-sensitive content. A password reset experiment should not expose a live user's reset token, personal data, or production account access to an external test mailbox. Preserve the parts of the template and route you are studying while keeping the payload safe to inspect.

Before sending, write down what you will hold constant and what you will change. A practical test record includes:

A run identifier, UTC send time, and template or application version.

The sending provider, relevant configuration, and visible From address.

The intended test mailboxes and their mailbox providers.

Provider event identifiers and the received message's Message-ID.

Authentication results from the receiver you trust.

The observed folder, observation time, and any inbox category.

Whether that mailbox has forwarding, filters, allowlisting, or other unusual settings.

Do not assume the provider's message identifier and the message header's identifier are interchangeable. SES documents its assigned ID separately from the original message ID available in event header fields. Keep the identifiers labelled so you can correlate the right records. 2

Choose the observation window before the test. If you inspect one mailbox immediately and another much later, report those times. A message that has not been observed by your cutoff should remain unresolved until there is evidence of what happened.

For a baseline, avoid manually moving messages or changing mailbox rules between runs. If you deliberately test a filtering rule, record it as part of the experiment. Otherwise, you may end up attributing a recipient-side change to a sender-side fix.

Report the denominator and the unknown results

Suppose you planned one test message for each of 20 mailboxes. At the chosen cutoff, you observe:

12 messages in the inbox.

3 messages in spam.

2 messages in another folder.

3 mailboxes with no confirmed outcome.

These are invented numbers for explaining the calculation.

You could report 12 / 20 = 60% of planned mailboxes with confirmed inbox placement. You could also report 12 / 17 ≈ 70.6% inbox placement among observed copies. Both calculations are valid descriptions of this sample, but they answer different questions.

Show the counts, denominator, and unresolved outcomes together. This example is not production data.

Do not silently discard the three unknown results or classify them as spam. An unresolved result could reflect a delay, a collection problem, or a delivery failure. Investigate the cause using provider events and the mailbox itself.

Define “inbox” before calculating the result. Gmail's Promotions and Updates tabs are inbox categories. They should not automatically become spam or “other” in a report. If your question is specifically whether a message reaches Primary, report Primary placement as a separate measurement. 7

Finally, break the observations down by provider. A single percentage can hide the only pattern that matters: one receiving service may behave differently from the others. Keep small sample counts visible; “one of two” communicates more about the evidence than an isolated “50%.”

Debug the failure you can actually observe

Once you have the provider events, the received copy, and the folder observation, choose the next change from that evidence.

If the sending request failed

Start with the response and the application logs. Confirm which request failed, which recipient it concerned, and whether your retry logic created another attempt. Changing the email's subject line is not a useful first response to an API validation error.

If the recipient server rejected or delayed the message

Read the provider's delivery, bounce, and delay information. Preserve the remote response and its timestamp. A rejection is a different problem from an accepted message appearing in spam, and a temporary delay needs a different interpretation from a completed delivery event. 11

Avoid flattening every event into a single email_failed state. You need enough detail to distinguish application errors, transport outcomes, and what a mailbox inspection later revealed.

If authentication failed

Check the actual sending source and the domains shown by the receiver. Then verify that your SPF and DKIM configuration covers the route you used and that the intended domain aligns for DMARC. A DNS record existing somewhere is not evidence that this particular message passed. 10

Google's sender guidelines also include infrastructure and sending requirements beyond a single authentication result, including TLS, valid DNS configuration, and additional requirements for bulk senders. Check the current requirements applicable to your traffic instead of copying an old configuration checklist. 8

If authentication passed but the message landed in spam

Use a controlled comparison to narrow the next investigation. Begin with a short, legitimate message sent through the same route, then restore the elements of the actual template in separate experiments:

The full business content and subject.

Links and their destination domains.

HTML layout and images.

Attachments, if the real workflow needs them.

Open tracking and link rewriting, tested separately.

Keep required unsubscribe functionality in place for messages that need it. Do not remove sender identity or make a marketing email look like a personal reply to improve a test result. Google's guidance explicitly addresses accurate headers, sending practices, and applicable unsubscribe requirements. 8

Treat a placement difference as a lead, then repeat the comparison. If an email reaches spam after a tracking change, you have a reason to inspect the rewritten URLs and repeat the test. You have not proved that tracking always causes spam, or that removing it will repair every sender's results.

For repeated comparisons, alternate the order of the variants and record send times. A single before-and-after run does not isolate every condition that may have changed between sends.

Some problems will not be explained by one template element. Review changes in sending volume, audience expectations, complaints, and sending infrastructure alongside the message. A formatting experiment cannot substitute for understanding how that email stream is being used.

Add placement evidence to your monitoring

An application can record a successful email submission while the user experience still fails. Model the evidence as separate observations with separate timestamps:

request_accepted_at: the sending service accepted the request.

recipient_server_accepted_at: a delivery event established acceptance by the recipient server.

placement_observed_at: a controlled mailbox inspection recorded a folder.

user_action_at: your application recorded the action you care about.

These are suggested application fields, not a provider's API schema. Preserve event history rather than repeatedly overwriting one status. Only populate a field when you have evidence for that event; leave production placement unknown when you cannot observe the destination mailbox.

For a password reset, the completed reset is a useful product outcome. For an invitation, it might be account activation. Those signals help investigate impact, but an absent action alone does not identify a delivery problem.

Open tracking also has limits. Apple says Mail Privacy Protection prevents senders from seeing whether a protected user opened a message. An open-rate chart therefore cannot serve as a direct measurement of inbox placement. 9

Add a small placement check to releases that change the sending provider, sender domain, signing configuration, template renderer, tracking domain, or important transactional email. Compare with a recorded baseline and decide whether the evidence justifies a larger investigation.

What a seed test cannot promise

A seed test describes the mailboxes, message, route, and observation window you tested. It is not a random sample of every customer's inbox.

If all of your controlled mailboxes show inbox placement, that is useful evidence that the tested route worked for that group. It does not prove that every user will see the same outcome, that every future campaign will behave identically, or that the message will receive attention.

The opposite matters too: a poor result in one controlled mailbox deserves investigation, but it does not establish that your entire audience received the message in spam. Check the pattern, reproduce it, and compare it with other evidence.

Common questions about inbox placement

Can SPF, DKIM, and DMARC all pass while the email lands in spam?

Yes. Authentication results and folder placement describe different things. Inspect the receiver's results, then investigate the observed placement and sending context. Passing authentication is not a promise of inbox delivery. 10

Can an SMTP response tell me the destination folder?

The receiving server's final positive SMTP response after message transfer establishes acceptance at that stage. For the actual folder, obtain a mailbox observation or another explicit placement report. A response from your sending service establishes an earlier stage, so identify which server produced it. 12

Is Gmail Promotions a failed inbox placement test?

Not when the question is inbox versus spam. Promotions is an inbox category. If your goal is specifically the Primary tab, define that separately and report it without relabelling Promotions as spam. 7

Can a header parser calculate my inbox placement rate?

The script in this article cannot. It extracts selected headers from a local file. A placement rate also needs folder observations, a definition of the eligible sample, and an explicit denominator.

A repeatable workflow

Collect the provider events. Save a received copy. Inspect the authentication results from a trusted receiver. Record the folder separately. Change one relevant part of the sending setup, repeat the test, and keep the unknown outcomes visible.

The most useful improvement is often the point where your team stops treating “sent,” “delivered,” and “seen in the inbox” as interchangeable states. Once each claim has its own evidence, the next debugging step becomes much easier to choose.

Sources

AWS SendEmail API

AWS SES event data and delivery objects

RFC 8601 Authentication Results

Google Workspace DMARC setup and alignment

Python email parser documentation

Python EmailMessage and get_all

Gmail inbox categories

Google email sender guidelines

Apple Mail Privacy Protection

Google Workspace DMARC troubleshooting

AWS SES sending activity and event types

RFC 5321 SMTP message acceptance

Disclosure: This article was prepared with AI assistance. The header example and sample counts are illustrative.

來源:DEV Community · dev.to