AI 资讯
Your Laravel Models Aren’t the Problem. Hidden Workflows Are.
Most Laravel applications do not become difficult to maintain because somebody made one obviously terrible architectural decision. They get there through dozens of small decisions that looked completely reasonable at the time. A controller grows beyond a comfortable size, so part of its logic moves into an Eloquent model. Later, the application needs to send a notification after an order ships. Then inventory must be checked through an external API. Accounting requests an ERP integration, and marketing wants loyalty points awarded after dispatch. Each change adds only a few lines. Since the Order model is already available wherever the feature is being implemented, adding one more method feels natural: $order -> ship (); It is concise, expressive, and pleasantly object-oriented. Six months later, however, that innocent method may be updating several tables, calling two APIs, dispatching events, sending notifications, and deciding whether the entire operation is allowed. The model has quietly become the place where the application runs its business workflows. That is the real problem. It is not that the model is “fat.” It is that persistence, domain decisions, and application orchestration have been mixed together until nobody can change one without understanding all three. Business Logic in Eloquent Models Is Not Automatically Wrong The common advice to remove all business logic from Laravel models goes too far. Eloquent follows the Active Record pattern. Martin Fowler describes Active Record as an object that represents a database row, encapsulates database access, and adds domain logic related to that data. In other words, an object containing both state and behavior is not an architectural mistake by itself. A model should be allowed to answer questions about itself: class Order extends Model { protected function casts (): array { return [ 'paid_at' => 'datetime' , 'expires_at' => 'datetime' , ]; } public function isPaid (): bool { return $this -> paid_at !== nul
科技前沿
How Data Centers Broke American Politics
What the Unabomber, Steve Bannon’s tech guy, and Bernie Sanders taught me about the great data center backlash of 2026.
AI 资讯
Can Reddit fend off a new wave of AI SEO spam?
Earlier this year, a Reddit user had asked members of a skincare-focused subreddit if anyone had tried a specific hypochlorous acid spray, a product often used for acne. There were dozens of responses; one from a user named Primary-Taro4254 seemed innocuous enough, at least at first. "I haven't personally tried [that brand] so I can't […]
开发者
The Real Story Behind the 2018 Google Walkout
In 2018, more than 20,000 employees walked out to protest how Google handled allegations of sexual harassment. Here’s how they organized it from inside the company.
AI 资讯
How to Make Claude Code and AI Coding Agents Smarter with Spec-Driven Development
A practical guide to writing specs that turn vague AI prompts into production-quality code — from functional requirements to edge cases, with real before-and-after examples. Let me paint a picture you've probably lived. You open Claude Code or OpenCode, type a vague prompt like "add a user dashboard with analytics," and hit enter. The agent spins up, writes a bunch of code — it even looks decent at first glance. Then you realize: the data model is wrong, the API endpoints don't match your existing patterns, there's no error handling, and the "analytics" is just a row of four hardcoded numbers. You spend the next hour correcting, prompting, correcting again. You would have been faster writing it yourself. Now imagine a different scenario. You spend 15 minutes writing a structured specification, paste it into the agent, and it produces exactly what you wanted — following your conventions, handling edge cases, wired into your existing auth and data layer. One shot. That's not luck. That's the difference between treating your AI coding agent like a chatbot and treating it like a senior engineer who needs a clear design document — also known as spec-driven development . The Core Problem: AI Agents Are Powerful but Undirected Claude Code, OpenCode, Cursor Agent — these tools are incredible when pointed at a well-defined task. They can read your entire codebase, understand your conventions, and produce production-quality code. But they share a fundamental limitation: they don't know what you want unless you tell them, precisely and completely. When you give an agent a one-liner prompt, you're leaving an enormous amount of ambiguity. The model will fill in the gaps — but it fills them with its own assumptions, which are often generic, incomplete, or just wrong for your context. A spec closes those gaps. It transforms an open-ended creative writing exercise into a constrained engineering task. What Makes a Spec Actually Work for AI Coding Agents? A spec that works for an AI
开发者
Stop hls.js from flapping between quality levels on cellular (with abrSwitchInterval)
TL;DR ABR "flapping" is when your player hops between quality levels every few seconds on a jittery network, and each hop is a visible lurch. We'll detect it from LEVEL_SWITCHED events, then fix it in layers: widen the bandwidth-estimator memory, make upswitches earn their place, and cap the switch rate with abrSwitchInterval (new in hls.js 1.7). Config + a detection snippet you can paste in today. 📦 Code: github.com/USER/hlsjs-abr-tuning, replace before publishing The bug nobody reports correctly Users don't file "my ABR is flapping." They say the video "kept changing" or "couldn't decide." What's happening: on cellular, throughput is spiky, and the player's bandwidth estimator treats every spike as the new truth. One fast segment and it jumps to 1080p, one slow segment and it drops to 240p, over and over. Low rebuffer ratio, good startup time, and still a miserable watch. Counterintuitively, feeding the player fresher bandwidth data makes this worse, because fresher data is noisier. The fix is a player with a longer memory and slower reflexes. Let's build that. 1. First, detect the flap 📊 Don't tune by vibes. Count level switches per minute of playback. Every switch fires Hls.Events.LEVEL_SWITCHED . // abr-monitor.js, hls.js 1.7.x, node 20+ tooling / any modern browser import Hls from " hls.js " ; export function attachFlapMonitor ( hls ) { const switches = []; hls . on ( Hls . Events . LEVEL_SWITCHED , ( _evt , data ) => { const now = performance . now (); switches . push ({ t : now , level : data . level }); // keep a 60s sliding window while ( switches . length && now - switches [ 0 ]. t > 60 _000 ) switches . shift (); const perMin = switches . length ; const reversals = countReversals ( switches ); if ( perMin >= 6 ) { console . warn ( `[abr] flapping: ${ perMin } switches/min, ${ reversals } reversals` ); } }); } // a "reversal" = up then down (or down then up), the signature of flapping function countReversals ( s ) { let r = 0 ; for ( let i = 2 ; i < s . l
AI 资讯
When "select all" checkboxes don't actually select anything — verifying after `check()`, not just trusting it
WordPress's plugin and theme update screens both have a "select all" checkbox. Calling check() on it with Playwright succeeds — no error, no exception. But look at the individual checkboxes afterward, and sometimes none of them are actually checked. Note: Playwright's check() ticks a checkbox. The click itself can succeed even if the page's JavaScript handler never fires, leaving what the form actually submits out of sync with what the screen visually shows. What actually happens The "select all" checkbox is usually wired up with a JavaScript handler: clicking it is supposed to check every individual checkbox underneath it. Playwright's check(force=True) can force the DOM state of that one checkbox — but that only changes that checkbox's own state . It doesn't guarantee the JavaScript handler that's supposed to propagate the change to the individual checkboxes actually fires. # Looks like it worked, but the individual checkboxes are still unchecked select_all . first . check ( force = True ) page . click ( ' input[type= " submit " ][name= " upgrade " ] ' ) Clicking the update button submits whatever the form's actual state is — which is "nothing checked." Nothing updates. No error is thrown, so on the surface it looks like the run completed normally. The fix — verify right after checking, every time Right after checking "select all," confirm that the individual checkboxes underneath are actually checked. If they aren't, fall back to checking each one individually. sel_all_sel = ' input[type= " checkbox " ][id^= " plugins-select-all " ] ' select_all = plugin_form . locator ( sel_all_sel ) if select_all . count () > 0 : select_all . first . check ( force = True ) page . wait_for_timeout ( 500 ) # Verification step — confirm checkboxes are actually checked chk_sel_check = ' input[type= " checkbox " ][name= " checked[] " ]:checked ' any_checked = plugin_form . locator ( chk_sel_check ). count () > 0 if not any_checked : # Select-all had no effect; switch to individual s
AI 资讯
Generating 10,000 certificates from one HTML template
The day your first cohort completes a course is the day certificates stop being a design job and become an engineering problem. One certificate is a Canva export. Ten thousand is a rendering pipeline with a database table, a queue and a verification page. This post walks through the three ways teams actually build that pipeline, with working Python for each, then covers the two parts most certificate tutorials skip: batching at volume and verification. It is a condensed version of our full guide, How to generate signed digital certificates at scale , which also covers storage, retention and revocation. One scope note up front. Most platform certificates do not need cryptographic signing in the PKI sense. The trust model that 95% of platforms ship is simpler: a unique ID printed on the certificate resolves to a verification page on the issuer's domain. An employer types the ID, the page confirms it. That is the model this post builds. If you need true PKI signing for regulated credentials, the stack is different (Adobe Sign, DocuSign, in-house HSM workflows) and this post is not it. What every certificate needs Whichever approach you pick, the output is the same: Component Detail Layout Landscape A4, 2480x1754 at 200 DPI for print Personal Recipient name with full Unicode support Course Course title and completion date Issuer Issuer name plus a signature image ID Unique certificate ID (UUID or short slug) Verify A URL under the ID pointing to your /verify route The signature image communicates authority but provides zero tamper resistance. The certificate ID plus the verification page is the practical trust layer. Keep both in mind as you read the code. The three approaches at a glance Approach Setup Render time Maintenance PDF library (ReportLab, PDFKit) 1 day 200 to 400 ms Fonts, layout drift, library updates HTML plus headless Chrome 2 hours 1 to 3 sec Chromium, memory, queue workers Template API 5 minutes 1 to 2 sec None Approach 1: a PDF library Python with Repo
AI 资讯
100 城时区页给跨区调度当速查,DST 自动算
100 城时区页给跨区调度当速查,DST 自动算 作者是 数据管道 / 跨时区调度 方向的开发者。这篇不是广告,是踩坑记录 + 顺手做的工具。 背景 做 数据管道 / 跨时区调度 时,时间戳转换是最常被低估的雷区。16 个时间戳工具(Unix 转换/时区/ISO8601/Cron/Duration…) 已覆盖日常;但每个语言/框架的坑都不一样,所以又补了 30 个语言/框架时间戳页(python/javascript/java/sql/…),每页含 6 个真实坑。 我踩过的坑(举几个) 秒 vs 毫秒:前端 Date.now() 是毫秒,后端常存秒,混用差 1000 倍。 时区不是字符串:存 UTC、展示本地,别把本地时间当 UTC 落库。 2038 问题:32 位系统 time_t 在 2038-01-19 溢出,老系统要提前查。 夏令时:一年有两次重复/缺失的本地时间,跨区调度尤其坑。 我顺手做的东西 转换速查页: https://gotimestamp.com/timezone/new-york 相关语言页: https://gotimestamp.com/timezone/london 开源 MCP: https://github.com/caresotin/tsforge-mcp —— 把时间戳转换/校验直接接进 LLM 工作流,不用手算。 小结 时间戳没那么简单,但工具到位就省心。上面都是免费、开源、可直接用的,希望对同样踩坑的人有帮助。
AI 资讯
Node Date 的 epoch 毫秒坑 + 用 MCP 把转换塞进 AI 流
Node Date 的 epoch 毫秒坑 + 用 MCP 把转换塞进 AI 流 作者是 Node.js / JS 时间 方向的开发者。这篇不是广告,是踩坑记录 + 顺手做的工具。 背景 做 Node.js / JS 时间 时,时间戳转换是最常被低估的雷区。16 个时间戳工具(Unix 转换/时区/ISO8601/Cron/Duration…) 已覆盖日常;但每个语言/框架的坑都不一样,所以又补了 30 个语言/框架时间戳页(python/javascript/java/sql/…),每页含 6 个真实坑。 我踩过的坑(举几个) 秒 vs 毫秒:前端 Date.now() 是毫秒,后端常存秒,混用差 1000 倍。 时区不是字符串:存 UTC、展示本地,别把本地时间当 UTC 落库。 2038 问题:32 位系统 time_t 在 2038-01-19 溢出,老系统要提前查。 夏令时:一年有两次重复/缺失的本地时间,跨区调度尤其坑。 我顺手做的东西 转换速查页: https://gotimestamp.com/timestamp/nodejs 相关语言页: https://gotimestamp.com/timestamp/javascript 开源 MCP: https://github.com/caresotin/tsforge-mcp —— 把时间戳转换/校验直接接进 LLM 工作流,不用手算。 小结 时间戳没那么简单,但工具到位就省心。上面都是免费、开源、可直接用的,希望对同样踩坑的人有帮助。
AI 资讯
Every Way to Export LinkedIn and Sales Navigator Data (and When Each One Actually Works)
A few months back I was running Sales Navigator searches for a client project — filtering down to "VP Sales, fintech, based in Italy or Spain" type lists — and the results were genuinely good. 60, 80 leads that actually matched. Then I hit the part nobody warns you about: there's no button on that page that says "save this." So I did what everyone does. Opened a spreadsheet, alt-tabbed back and forth, typed names and job titles by hand. Around profile 40 I gave up and went looking for a better way. This is what I found, roughly in the order I found it, including the tool I ended up building because none of the existing options quite fit what I needed. First: the export LinkedIn actually gives you LinkedIn has a real, built-in data export, and most people don't realize how narrow it is. It's under your profile photo → Settings & Privacy → Data Privacy → Get a copy of your data . From there you either tick specific categories (that email usually lands within minutes) or request the full archive, which takes closer to a day and sometimes arrives in two batches. Either way you get a download link that expires after 72 hours — and it's desktop only, the mobile app won't let you request one. What you get back is genuinely thorough: connections, messages, your own profile history, activity, even the ad-targeting data LinkedIn holds on you. A couple of quirks worth knowing before you rely on it: some connections' email addresses will just be missing, because sharing an email on download is something each person opts into individually, and you won't get a list of who viewed your profile or any "People You May Know" data. If you're in the EU, EEA, or Switzerland, LinkedIn also runs a separate API for pulling your data on a schedule rather than as a one-off request. Here's what this export is not built for, though: it has no idea what you searched for yesterday. It's an archive of your own account, not a way to capture a live search. Run a Sales Navigator query and pull 80 lea
AI 资讯
Base Power raises another $1B to save the grid using backyard batteries
Base Power’s $1 billion round will help the startup ramp production of its home batteries.
AI 资讯
Java News Roundup: OpenJDK JEPs, Jakarta EE, GraalVM, TornadoVM, Micronaut, Quarkus, JobRunr, Maven
This week's Java roundup for July 27th, 2026, features news highlighting: OpenJDK JEPs targeted and proposed to target for JDK 28; the GA release of GPULlama3.java 1.0; point releases of Micronaut, Quarkus and JobRunr; a maintenance release of JDKUpdater; the sixth release candidate of Maven 4.0; and the first milestone release of Jakarta Agentic AI 1.0. By Michael Redlich
AI 资讯
Health Checks and Uptime Monitoring: API Polling, 429 Backoff, and Retry Patterns
If you just want the recommendation: build the uptime poller yourself, put exponential backoff with jitter in front of every health check, and treat a 429 as a scheduling signal instead of an error you swallow. Query-style observability APIs hand you metrics and logs, not threshold rules or notification channels, so the polling worker is the thing that has to decide what "down" means and who gets woken up. That decision is the whole job. I got burned by exactly this. What follows is the pattern that survived the postmortem, the alternatives I weighed before writing a line of it, and the conditions where you should not do any of this yourself. The 429 my retry loop ate for six hours Last spring I was running a homegrown health checker for 40 internal services. One goroutine per service, all driven off the same 15-second ticker, which meant every check landed inside the same 200ms window. The status API we polled had a per-minute quota I'd never bothered to read, and for months it didn't matter, because 40 checks a minute sat comfortably under the ceiling. Then a colleague onboarded 12 more services, we crossed the quota, and the API started answering with HTTP 429. My retry wrapper caught it, retried three times in a tight loop, and on the last attempt returned the previous cached result — which said healthy . It logged the rate limit at debug level. Nobody reads debug. Six hours. Green dashboard. Dead queue consumer. We found out when a customer asked where their export was. The consumer had died on an unrelated deploy, the checker never noticed, and when I finally restarted it the backlog got re-processed on top of a manual replay I'd already run — two customers got the same notification twice. Duplicate deliveries are the specific thing I lose sleep over, and I had caused a batch of them with a retry loop that was trying to be helpful. The postmortem produced one line I now paste into every runbook: a check that can't reach the API reports unknown, never healthy.
AI 资讯
Git Graph Explained: Visualizing Merge, Rebase, and Cherry-Pick
Git is the ultimate tool for developers. Yet, branching strategies still confuse many of us. Commands like merge, rebase, and cherry-pick manipulate your commit history in completely different ways. If you just guess what they do, you risk ruining your team's shared history or losing track of your changes. The easiest way to understand Git is to visualize it. Let us look at exactly what happens to your Git graph when you run these three critical commands. 🏗️ Starting Point: Our Example Repository Imagine we have a standard repository. We branched off the main branch from commit B to work on a new feature in a feature branch. While we worked on our feature, someone else pushed commit C and D to main. Here is what our history looks like right now: C --- D [main] / A --- B \ E --- F [feature] main has two new commits: C and D. feature has two new commits: E and F. 🔀 1. Git Merge (The Safe Record Keeper) When you merge main into your feature branch (or vice versa), Git creates a special, brand-new commit called a merge commit. git checkout feature git merge main The Visual Graph After Merge: C ------- D ------ [main] / \ A --- B \ \ v E --- F --- G [feature] What happened under the hood? Git looked at the common ancestor (B), took the history of main (C and D), took the history of feature (E and F), and combined them. Commit G is the merge commit. It has two parent commits: F and D. Pros: 100% non-destructive. It preserves the exact historical timeline of when things actually happened. Cons: Your Git graph can quickly become a messy "train track" web if you have many developers merging constantly. 🚀 2. Git Rebase (The Clean History Rewriter) Rebase takes all the commits from your current branch, lifts them up, and replants them on top of the very last commit of the target branch. git checkout feature git rebase main The Visual Graph After Rebase: C --- D [main] / \ A --- B E' --- F' [feature] What happened under the hood? Git temporarily blew away commits E and F. It ca
AI 资讯
How to Remember Namespaces
I often see people using the term "namespace" incorrectly. Even when explanations of what a namespace is are presented, they only go as far as describing its function, neglecting to properly define the name "namespace" itself. Definition of the Namespace A namespace is literally the space to which a name belongs . If we were to classify the term namespace, it would be a specification (concept), not a tool. In namespaces, the higher level is represented as outer and the lower level as inner . In terms of class structure, this corresponds to outer classes and inner classes. In other words, to explain it from a different perspective, it looks like this. Representation of namespaces from the outer perspective Build namespaces (best) Define namespaces (to fit many programming language implementations) Open namespaces (such as Ruby's class definition and module definition ) Create namespaces (such as the pseudo-namespace hack in older JavaScript) Declare namespaces (such as the package declaration in Java or the namespace declaration in PHP) Representation of namespaces from the inner perspective Belong to a namespace (best) Entering the namespace (This is entirely from an inner perspective, so it might feel out of place depending on the context) Be included in the namespace (this is a reasonable explanation if explained objectively). Incorrect expression From the definition above, it is clear that the following expressions are incorrect. Add/Paste a namespace (the expression "add/paste a space" is grammatically incorrect). Use namespaces (not to the point of being completely broken, but treating namespaces as a tool) Separate/Cut namespaces (While "Separated by namespaces" is understandable, "separate/cut" can be misleading) Meaning of "Name" in Namespace The "names" referred to here can be class names, module names, or package names. What they represent varies depending on the language that implements namespaces. For example, in Ruby, it refers to constant names. In Rub
AI 资讯
Self-Hosted SSO from Scratch with Laravel Passport
A hands-on guide to being your own Identity Provider — with Laravel 12 and Passport v13. You will build a Central Portal that acts as an OAuth 2.0 Authorization Server, then wire up child sites ( Site A , Site B , Site C ) so a user logs in once and gets access to all of them. No Google. No Auth0. No Keycloak. No "Sign in with…" anything. You own the users table, you issue the codes and tokens, you hold the signing keys. The only dependency is laravel/passport , which implements the OAuth 2.0 protocol machinery — every identity decision is yours to make, and this guide walks through each one. The scope is deliberately narrow: authentication only. How a user proves who they are at a central server, and how a child site learns that identity. Everything else (admin CRUD screens, audit logging, UI theming) is left out. Everything here is buildable on a fresh Laravel install. No prior context needed. Table of Contents What We Are Building OAuth 2.0 Foundations System Architecture Authentication Workflows Part A — Building the Central Portal Part B — Building a Child Site Registering a Child Site End-to-End Testing Gotchas & Security Notes Reference Tables Appendix A — Extending to Multiple User Types Appendix B — Mental Model in One Page 1. What We Are Building The Problem You operate several web applications. Each has its own users table, its own login form, its own password reset flow. When a staff member joins, someone creates four accounts. When they leave, someone must remember to disable four accounts. Passwords drift out of sync. There is no single place to answer "who has access to what?" The Solution One central server owns identity. Every child site delegates login to it. ┌─────────────────────────────┐ │ Central Portal │ │ ┌───────────────────────┐ │ │ │ admin.portal.test │ │ Management UI │ │ │ │ — create users │ └───────────────────────┘ │ — grant per-site access │ ┌───────────────────────┐ │ │ │ sso.portal.test │ │ OAuth 2.0 endpoints │ │ (authorization ser
AI 资讯
When `update-core.php` version scraping goes wrong — telling a plugin version number apart from WordPress core
On hosting without SSH access, a common pattern is to open update-core.php (the WordPress update screen) with Playwright and read "what version is WordPress core currently running" straight off the page text. In one deployment, the recorded core version came back as something like 1.7.11 — a number that has never existed for WordPress core. Note: update-core.php is the WordPress admin's "Updates" page, listing pending updates for core, plugins, themes, and translations all on one screen. What was actually happening That page doesn't only show the core version string — it's packed with version numbers belonging to pending plugin and translation updates too. A line like "Update Plugin X to 1.7.11" is typical. # First implementation — grabs the first N.N.N-shaped number on the page match = re . search ( r ' \d+\.\d+(?:\.\d+)? ' , page_text ) version = match . group ( 0 ) if match else None This naive regex grabs whatever N.N.N -shaped number appears first on the page. Because of how the page is laid out, a plugin's pending update can render above the core version message, so 1.7.11 (a plugin's version) ended up recorded as the WordPress core version. Why this is hard to catch The bug doesn't throw an exception — the regex matches successfully, just on the wrong value. Nothing about it looks broken until someone notices the report shows a version that WordPress core has never shipped (there's no 1.x series for core). The fix — a three-stage guard Prefer a dedicated selector first — look for specific DOM locations where core's update message actually renders, like p.response > strong or #wp-version-message strong Fall back to keyword-anchored regex — if no selector matches, only accept a number immediately following the words "WordPress," "バージョン," or "Version" Validate plausibility as a final check — whichever path produced a value, run it through a function that checks the major version number falls within 4–9 def is_plausible_wp_core_version ( ver : str ) -> bool : m =
AI 资讯
GitLab 2FA Lockout: How My Local SSH Key Saved the Day
I have two-factor authentication (2FA) enabled on most of my accounts using an authenticator app. Recently, while installing the app on another Android device, I tried to change the backup password, but it didn't work. As a result, I lost access, had to disable 2FA, and re-enable it using a different authenticator app. Setting up 2FA again wasn't a problem because I was still logged in to most of my accounts. However, I didn't have my GitLab recovery codes. GitLab offers only two ways to regain access: receiving a six-digit verification code via email or generating new recovery codes using an SSH key associated with the account. Receiving a verification code via email is the easiest way to recover your account, but having an SSH key can be incredibly useful when receiving an email verification code isn't an option. Whenever I configure GitLab in my local environment, I create an SSH key for authentication and commit signing, as I always sign commits in my repositories. I described this process in a previous article . Get New Recovery Codes Check the SSH keys on your machine: ls -la ~/.ssh Look for files named like id_rsa , or id_ed25519 . Run the following command to get new recovery codes: ssh -i ~/.ssh/id_ed25519 git@gitlab.com 2fa_recovery_codes Replace id_ed25519 with the name of your SSH key file. Copy one of the recovery codes Go to the sign in page Enter your username and password Provide the recovery code when prompted Now you're signed in! Disable 2FA and re-enable it—and don't forget to save your recovery codes somewhere safe this time.
AI 资讯
Why your DMARC reports never arrive (and why Gmail can't be your rua address)
You published a DMARC record months ago. Every checker says it's valid. No reports have ever arrived, and at some point you stopped thinking about it and quietly concluded nobody's forging your domain. You don't know that. You just can't see. Two causes account for almost all of it, and both leave the DMARC record itself looking perfect. 1. Your reporting address never agreed to receive them This is in the spec and almost nobody knows about it. If your rua= address is on a different organisational domain from the domain publishing the record, RFC 7489 section 7.1 requires the destination to explicitly consent. The reason is obvious once you see it: otherwise anyone could point rua= at your mailbox and use the world's mail infrastructure as a free DDoS. Consent is a DNS record, published by the receiving domain: <your-domain>._report._dmarc.<their-domain>. TXT "v=DMARC1" So if example.com sends reports to dmarc@reports.vendor.com , then vendor.com has to publish: example.com._report._dmarc.reports.vendor.com. TXT "v=DMARC1" Without it, conforming reporters send nothing at all. That includes Google, which is most of your report volume. You can watch this working in production. PayPal sends aggregate reports to a third party, and the authorisation record exists: paypal.com._report._dmarc.rua.agari.com. TXT "v=DMARC1;" The practical consequence: not a Gmail address You cannot put a Gmail address in rua= . Google is not going to publish yourdomain.com._report._dmarc.gmail.com on your behalf, and you can't create records in their zone. Same for Outlook, Yahoo, or any address at a domain you don't control. This is a common first move — publish DMARC, point reports at your personal inbox, wait. Nothing arrives, and nothing is wrong with the record. If you use a commercial DMARC service they normally publish these records during onboarding, often via a wildcard. If reports never start after you sign up, check this before anything else. When it doesn't apply: reporting to you