SPF is one TXT record that lists the servers allowed to send mail as your domain.
Most failures come from three mistakes: two records, a missing sender, or more than 10 DNS lookups. This guide publishes the record, tests it, and fixes each of those. For the definition, see what SPF is.
TL;DR
- SPF takes about 30 minutes to set up and test, plus DNS propagation.
- You inventory every sender, publish one
v=spf1TXT record, count its lookups, and prove a real message passes. - It spends 0 LeadOcean records. The list check at the end uses a free count.
Prerequisites
- Edit access to the DNS zone of your sending domain.
- A shell with
dig. - A mailbox you own at another provider, to read the result headers.
- A LeadOcean API key in
$LEADOCEAN_API_KEYfor Step 6. The free plan covers it. - A list of every system that sends mail as your domain: mailboxes, sequencer, help desk, product.
acme.com stands for your domain. Replace it everywhere.
Step 1: Check what is already published
A domain may have only one SPF record. RFC 7208 says that if more than one v=spf1 record exists, the result is permerror (RFC 7208, September 2026). Look before you add anything.
dig +short TXT acme.com | grep spf1If a record comes back, you will edit it, not add a second one. This is a live query for the example.com test domain, run on 21 October 2026:
$ dig +noall +answer TXT example.com @8.8.8.8
example.com. 300 IN TXT "v=spf1 -all"v=spf1 -all is the "this domain sends no mail" record. Other TXT records on the same name are fine, such as site verification tokens. Only v=spf1 ones count.
Step 2: List every sender and its include
Each system that sends as your domain needs one mechanism in the record. Your provider's help page gives the string.
Sender Mechanism
Google Workspace include:_spf.google.com
Sequencer include:<value from its setup page>
Your own server ip4:203.0.113.25Google publishes v=spf1 include:_spf.google.com ~all for Workspace, and adds include:amazonses.com as an example for a second sender (Google, October 2026). Use ip4: for a fixed address. The address above is a documentation range.
Do not add a sender you do not use. Every include: costs lookups, and old ones are the usual reason a record breaks.
Step 3: Write the record and pick the ending
Join the mechanisms into one string that starts with v=spf1 and ends with an all qualifier.
v=spf1 include:_spf.google.com ip4:203.0.113.25 ~allThe ending decides what receivers do with mail from unlisted servers:
-allis fail. The strictest choice.~allis softfail. Mail is accepted but marked. Google's own example uses it.?allis neutral and+allallows everyone. Never use+all.
Start with ~all. Move to -all once reports and test sends show every sender passes.
Step 4: Publish and verify it resolves
Create one TXT record at the root of the domain (host @) with the string from Step 3. Then query it from outside your network:
dig +short TXT acme.com @8.8.8.8 | grep spf1You should see your string back in quotes. Two common slips: pasting the value with smart quotes, and creating the record at www.acme.com instead of the root. SPF is checked on the domain in the envelope sender, so it must sit on that exact name.
Step 5: Count lookups and test a real send
SPF allows at most 10 DNS lookups. include, a, mx, ptr, exists and redirect each count. ip4, ip6 and all do not. Go over and the result is permerror (RFC 7208, September 2026). The RFC also limits lookups that return nothing to two.
Count by hand. Each include costs one lookup, plus whatever its own record contains:
dig +short TXT _spf.google.com @8.8.8.8On 21 October 2026 this returned a flat list with no further includes:
"v=spf1 ip4:74.125.0.0/16 ip4:209.85.128.0/17 ip6:2001:4860:4864::/56 ip6:2404:6800:4864::/56 ip6:2607:f8b0:4864::/56 ip6:2800:3f0:4864::/56 ip6:2a00:1450:4864::/56 ip6:2c0f:fb50:4864::/56 ~all"So that include costs one lookup. Repeat the query for every include in your record and add the numbers. Providers can change these records, so recheck after a vendor change.
Now send a real message from your domain to a mailbox you own and open the raw headers. Look for the result line:
Authentication-Results: mx.example.net;
spf=pass smtp.mailfrom=acme.comspf=pass with your domain next to smtp.mailfrom is what you want. softfail means the sending server is not in the record. permerror means the record is broken.
Step 6: Check the list before you send
SPF proves the server may send. It does not make a bad list safe. Bounces hurt a domain that passes every check.
LeadOcean stores an email_status on each address. Sizing is free. Send count=true as a query parameter with limit 1 and read meta.total:
curl -s -X POST "https://api.leadocean.io/v1/people/search?count=true" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jobLevel": ["VP"],
"jobFunction": ["Information Technology"],
"country": ["US"],
"limit": 1
}'The MCP tool leadocean_count_leads does the same from Claude or any MCP client. For the DKIM half of authentication, read Gmail DKIM. Sending at volume also changes your cost per contact: see what 1M records cost at 20 data providers.
What you get
Two outputs. The first is the SPF lookup from Step 1, run on 21 October 2026:
example.com. 300 IN TXT "v=spf1 -all"The second is a free count from leadocean_count_leads. It ran on 21 October 2026 and spent 0 records. The filters were VP, Information Technology and country US.
| Filter | Total |
|---|---|
| Default mailable (verified, catch_all_valid or catch_all) | 8,675 |
| No email filter | 17,699 |
| Distinct companies in the mailable count | 3,502 |
The first row is the audience worth sending to. The gap to the second row is addresses you would be guessing on. LeadOcean does not refund bounced emails, so read email_status before a send.
The REST sizing call returns the response shape from the API reference: success, an empty data array, and a meta object with countOnly: true and total. meta.total is capped at 100,000.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
spf=permerror | Two v=spf1 records on the name | Merge them into one record |
spf=permerror | More than 10 lookups | Remove unused includes, or replace an include with ip4: ranges |
spf=softfail | The sending server is not listed | Add that sender's include, then retest |
spf=none | No record at the envelope-sender domain | Publish at the exact name in smtp.mailfrom |
| 401 from the LeadOcean API | Missing or wrong x-api-key header | Check $LEADOCEAN_API_KEY |
| 429 from the LeadOcean API | Above your plan's rate (5 a second on Free, 200 on Agency) | Slow the client and wait the Retry-After seconds |
| 402 from the LeadOcean API | Free plan: the 1,000 one-off records are spent (counts cost nothing) | Check GET /v1/account, or move to Solo at $97 or Agency at $497 a month |
FAQ
Can I have two SPF records?
No. Two v=spf1 records give permerror. Merge every sender into one record.
Should I end with ~all or -all?
Start with ~all. Switch to -all once every sender passes in test sends. -all is stricter but punishes a forgotten sender.
Does SPF alone get cold email into the inbox?
No. SPF checks the envelope address, not the From address the reader sees. Add DKIM and DMARC, and keep the list clean.
Does LeadOcean send email or publish SPF records?
No. LeadOcean is a people and company data API with a hosted MCP server. It gives you the list and the email_status on each address. Your mailbox provider or sequencer sends.
Publish SPF, then fill the list with addresses you can trust
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →See also LeadOcean pricing. More guides are in the blog.