CiC TASK — Authenticate the 360degreelogicalmktg.com domain in Brevo so outbound email is signed and delivered from our own domain (human-in-the-loop) Purpose: today the Recce Portal's transactional emails go through Brevo but the return-path shows up as "via brevosend.com" in headers. Big mailbox providers (Gmail, Outlook, corporate spam filters) treat that as a yellow flag and may drop emails into Promotions / Spam. After this, every email from us is signed with our own DKIM, passes SPF for the 360degreelogicalmktg.com domain, and appears in the Primary inbox even for strict corporate clients (HP, Lenovo, Philips etc.). This is the recommended "step 4b" from the earlier Brevo PROD setup — required before any real brand-client user signs in. OBJECTIVE Walk me (the human) through: 1) confirming Brevo already has the domain registered (auto-created when the first single sender was added), 2) reading the four DNS records Brevo wants (DKIM x2, brevo-code TXT, DMARC TXT) and the one record to MODIFY (existing SPF), 3) pasting them at Hostinger (hPanel — our registrar), 4) verifying DNS propagation from the VPS, 5) pinging Brevo's /authenticate endpoint until verified=true and authenticated=true, 6) switching the worker's BREVO_FROM_EMAIL from the verified single sender to a clean per-app address (no-reply@360degreelogicalmktg.com), 7) smoke-testing end-to-end. End state: Brevo's domain object shows verified=true AND authenticated=true, a smoke-test email shows DKIM=pass + SPF=pass for 360degreelogicalmktg.com in Gmail's "Show original" headers, and arrives in Primary (not Promotions). GROUND RULES - Human-in-the-loop. PAUSE and hand control back to me whenever a step needs: Hostinger login credentials, OAuth consent, email verification link, CAPTCHA, phone OTP, or any actual DNS edit at the registrar. - NEVER type my Hostinger password, NEVER complete OAuth on my behalf. - The Brevo API key is already in /root/360lm-web/.env. DO NOT echo or leak it back to chat. When you need it, read it from the file (`grep ^BREVO_API_KEY= /root/360lm-web/.env | cut -d= -f2-`) into a shell variable and use that. - DO NOT use --no-verify, --no-gpg-sign, or any "bypass" flags. - This change is REVERSIBLE end-to-end. The Google Workspace MX record stays put — incoming mail is untouched throughout. - Read each registrar screen aloud (summarize what I'm looking at), tell me exactly what to click, then wait. PRE-CHECK (do these first, no human action needed) A. Confirm Brevo already has the domain registered (created automatically when we added the single sender business@360degreelogicalmktg.com): KEY=$(grep ^BREVO_API_KEY= /root/360lm-web/.env | cut -d= -f2-) curl -sS -H "api-key: $KEY" -H "accept: application/json" \ https://api.brevo.com/v3/senders/domains/360degreelogicalmktg.com \ | python3 -m json.tool Expected: domain object with verified=false, authenticated=false, and the four dns_records (dkim1Record, dkim2Record, brevo_code, dmarc_record) all with status=false. If verified=true already, JUMP to step 7. B. Confirm the registrar is Hostinger: dig +short NS 360degreelogicalmktg.com Expected: ns1.dns-parking.com / ns2.dns-parking.com (= Hostinger's default nameservers, managed in hPanel). If you see Cloudflare / GoDaddy / other nameservers — STOP and ask me to use that panel instead; the records are the same but the click-paths differ. C. Inspect current DNS to know what NOT to touch: dig +short MX 360degreelogicalmktg.com # expect: smtp.google.com dig +short TXT 360degreelogicalmktg.com # note existing SPF dig +short A 360degreelogicalmktg.com # current website IPs Read these out loud so I know what I'm leaving alone. STEPS 1. OPEN HOSTINGER hPANEL. Go to https://hpanel.hostinger.com. -> HANDOFF: PAUSE while I sign in. If OAuth / SMS OTP appears, PAUSE. When the dashboard loads, click Domains -> 360degreelogicalmktg.com -> in the left sidebar click "DNS / Name servers" -> scroll to "DNS records". 2. CONFIRM THE EXISTING SPF RECORD. Find the TXT record at name "@" whose value starts with "v=spf1". Read it to me. Expected: v=spf1 include:_spf.google.com ~all This is Google Workspace's SPF — for receiving via Gmail. We will MODIFY this in step 6. Do not delete it. 3. ADD THE BREVO-CODE TXT RECORD (Record 1 of 5). In the editor: Add new record. Type=TXT, Name=@, TTL=300. Value: brevo-code:09af5f67f93389cd0b86fe37e94d2f7b (Pull the exact value from the pre-check A response — it's the brevo_code.value field. Match it character-for-character; this is the ownership-proof token Brevo gave us. If you echo a different one, the verification will fail.) Save. PAUSE while I confirm the record appears in the list. 4. ADD DKIM RECORD 1 OF 2 (Record 2 of 5). Type=CNAME, Name=brevo1._domainkey, TTL=300. Value: b1.360degreelogicalmktg-com.dkim.brevo.com (Note Brevo replaces dots in the domain with hyphens in the DKIM hostname. Do NOT type the period after 'com' at the end.) Save. PAUSE. 5. ADD DKIM RECORD 2 OF 2 (Record 3 of 5). Type=CNAME, Name=brevo2._domainkey, TTL=300. Value: b2.360degreelogicalmktg-com.dkim.brevo.com Save. PAUSE. 6. MODIFY THE EXISTING SPF RECORD (Record 4 of 5) — DO NOT ADD A NEW ONE. This is the single most error-prone step. RFC 7208 allows only ONE TXT record beginning with "v=spf1" per domain. If two exist, BOTH are silently invalid and outgoing email from Brevo will keep getting rejected. Tell me this rule before I touch the editor. Find the existing v=spf1 TXT record at @ (the Google one from step 2). Click Edit (the pencil icon — NOT delete then re-add, NOT "duplicate", NOT "create new"). Replace the value with: v=spf1 include:_spf.google.com include:spf.brevo.com ~all Set TTL=300. Save. PAUSE while I confirm the record list still shows exactly ONE TXT at @ starting with v=spf1. 7. ADD THE DMARC TXT RECORD (Record 5 of 5). Type=TXT, Name=_dmarc, TTL=300. Default value (reports go only to Brevo): v=DMARC1; p=none; rua=mailto:rua@dmarc.brevo.com Alternate value (Brevo + a copy of aggregate reports to us at postmaster@): v=DMARC1; p=none; rua=mailto:rua@dmarc.brevo.com,mailto:postmaster@360degreelogicalmktg.com Ask me which I want; default is the alternate so I can see DMARC reports too. Explain that p=none means MONITOR-only — it will not block any email. Save. PAUSE. 8. VERIFY DNS PROPAGATION FROM THE VPS. Wait 2–5 minutes after the last save in Hostinger, then run from the VPS: dig +short TXT 360degreelogicalmktg.com dig +short CNAME brevo1._domainkey.360degreelogicalmktg.com dig +short CNAME brevo2._domainkey.360degreelogicalmktg.com dig +short TXT _dmarc.360degreelogicalmktg.com Expected: • The TXT query at the root shows TWO lines: the v=spf1 (now with include:spf.brevo.com) AND the brevo-code:… line. If either is missing, Hostinger didn't apply that record — pause and re-save. • Both brevo1/brevo2 CNAMEs resolve to b1./b2.360degreelogicalmktg-com. dkim.brevo.com. • _dmarc TXT shows the v=DMARC1 line. If any line is empty after 10 minutes, ask me to re-check Hostinger; the record was probably saved with a typo or wrong "name" prefix (e.g. "brevo1._domainkey.360degreelogicalmktg.com" instead of just "brevo1._domainkey" — Hostinger usually appends the domain itself). 9. TRIGGER BREVO RE-VERIFICATION + POLL. With KEY still in the shell variable from pre-check A: curl -sS -X PUT -H "api-key: $KEY" -H "accept: application/json" \ https://api.brevo.com/v3/senders/domains/360degreelogicalmktg.com/authenticate Then poll the domain object once at +10 minutes and again at +30 minutes (NOT faster — Brevo rate-limits and rapid polls don't speed it up): curl -sS -H "api-key: $KEY" -H "accept: application/json" \ https://api.brevo.com/v3/senders/domains/360degreelogicalmktg.com \ | python3 -c "import sys,json; d=json.load(sys.stdin); \ print('verified:', d['verified'], 'authenticated:', d['authenticated']); \ print({k: v['status'] for k,v in d['dns_records'].items() if v})" Expected within 30 minutes: verified=true, authenticated=true, all four dns_records show status=true. If after 30 minutes the brevo_code TXT shows status=false, jump back to step 8 — DNS hasn't propagated yet. If dkim1/dkim2 show false but brevo_code shows true, the CNAME hostnames are typo'd at Hostinger; ask me to re-check step 4/5. 10. SWITCH THE WORKER'S FROM ADDRESS. Now that the domain is authenticated, the FROM address no longer has to match the single-sender we verified earlier (business@…). Any local-part on the authenticated domain works. Pick the cleanest: BREVO_FROM_EMAIL=no-reply@360degreelogicalmktg.com BREVO_FROM_NAME=360 Degree Logical Marketing — Recce Portal Edit the env safely (do not echo the key around it): sed -i 's#^BREVO_FROM_EMAIL=.*#BREVO_FROM_EMAIL=no-reply@360degreelogicalmktg.com#' \ /root/360lm-web/.env sed -i 's#^BREVO_FROM_NAME=.*#BREVO_FROM_NAME=360 Degree Logical Marketing — Recce Portal#' \ /root/360lm-web/.env grep -E '^BREVO_FROM_(EMAIL|NAME)=' /root/360lm-web/.env Recreate the worker so it re-reads the env: cd /root/360lm-web && docker compose up -d --force-recreate counters-mail-relay sleep 3 && docker logs counters-mail-relay --tail 8 Expected: worker prints "starting · mode=PROD (Brevo)" and the FROM line shows the new address. 11. END-TO-END VERIFY. Send a real test email: docker exec postgres psql -U lmadmin -d lm360 -c \ "INSERT INTO counters.mail_outbox (to_email, to_name, subject, body_text, body_html, reference) VALUES ('haris.lal.1974@gmail.com', 'CiC test', 'Brevo domain-auth smoke test', 'If you see this in Primary inbox, domain auth is live.', '
If you see this in Primary inbox, domain auth is live.
', 'cic-brevo-domain-auth-' || EXTRACT(EPOCH FROM NOW())::BIGINT);" Within 60s check the row flipped: docker exec postgres psql -U lmadmin -d lm360 -t -c "SELECT message_id, status, sent_at, error FROM counters.mail_outbox WHERE reference LIKE 'cic-brevo-domain-auth-%' ORDER BY message_id DESC LIMIT 1;" Expected: status=sent, sent_at populated, error NULL. PAUSE and ask me to: (a) Confirm the email landed in Gmail PRIMARY (not Promotions). (b) Open the email -> three-dot menu -> "Show original" -> verify headers contain "SPF: PASS with IP …" AND "DKIM: PASS with domain 360degreelogicalmktg.com" AND "DMARC: PASS". (c) Note the From: line now reads "360 Degree Logical Marketing — Recce Portal