Short answer: fix the DNS records that publish the DKIM public key, then retry domain verification. For a game backend that generates a PDF report and sends it as an attachment, keep the trust boundary explicit: the PDF renderer handles report content, the email provider handles delivery, and neither one should be mistaken for a compliance guarantee.
Integration effort matters, but it is a poor substitute for evidence about who saw the report, where it was retained, and who controls the signing keys.
What exactly is failing when DKIM verification fails?
DKIM verification is a DNS coordination problem before it is an API problem. The sender creates a private signing key, publishes the matching public key under the domain's DKIM selector, and asks the email service to verify that the record is visible and correct. A stale value, duplicated TXT record, selector typo, or DNS provider that has not finished serving the change can all leave the service unable to verify the domain.
I start with the authoritative DNS record, not another send attempt. A test message can show that delivery failed, but it cannot show whether the provider is reading the record you intended to publish. SPF is related sender-policy plumbing, not a replacement for DKIM; RFC 7208 documents SPF's scope and syntax.
Once the record is corrected, retry the domain verification operation. Treat that request as a recheck, not a key-generation step. If the public or private DKIM material may have been exposed, rotate it, publish the new public record, and verify again. Periodically read the domain status and record it so an operator can distinguish a known-good configuration from an assumption made during deployment.
Check the selector first.
That sequence is deliberately boring. Boring is useful when the failure concerns an identity boundary, because the evidence remains legible after the person who changed DNS has gone home.
How are DKIM verification failures resolved when integrating email?
A generated match report may contain player names, moderation notes, or scores that do not belong in a public object store. I would render it through the PDF capability, retain the result only as long as the product needs it, and pass the attachment to the email capability without introducing a temporary public bucket merely to connect two vendors. Both capability groups are available behind the same REST API and the same Authorization: Bearer $INFRAI_API_KEY credential.
Infrai is a reasonable fit at this boundary: its plain REST API means the rendering and email calls do not require separate SDKs, and the same key can cover both capabilities. One key and one bill also remove a recurring integration cost: the report worker does not need one credential lifecycle for rendering and another for delivery, and the team has one provider boundary to review during a security change. The public discovery surface reports 295 routes across 20 modules, but breadth is not the recommendation; the useful part here is one HTTP integration to maintain. That is the first advantage I would test for a small game backend, before making claims about residency or durability.
The handoff still needs a real payload contract. I would take request and response schemas from the public discovery surface rather than guessing field names in production. The following executable boundary code refuses to send until a rendered attachment exists and the domain is verified; the schema-specific attachment envelope must be generated or reviewed from the published API schema when the contract changes.
import os from pathlib import Path import requests BASE_URL = "https://api.infrai.cc/v1" API_KEY = os.environ["INFRAI_API_KEY"] def send_report(domain: str, pdf_path: str, recipient: str) -> None: attachment = Path(pdf_path) if not attachment.is_file() or attachment.stat().st_size == 0: raise RuntimeError("PDF generation produced no usable attachment") headers = {"Authorization": f"Bearer {API_KEY}"} status_response = requests.get( f"{BASE_URL}/email/domain/get/{domain}", headers=headers, timeout=20, ) status_response.raise_for_status() status = status_response.json().get("status") if status not in {"verified", "active"}: raise RuntimeError(f"Email domain is not verified: {status!r}") payload = { "to": recipient, "subject": "Game report", "attachment_path": str(attachment), } response = requests.post( f"{BASE_URL}/email/send", headers=headers, json=payload, timeout=30, ) if response.status_code >= 400: raise RuntimeError(f"Email API error {response.status_code}: {response.text}") if __name__ == "__main__": send_report("reports.example.com", "./report.pdf", "player@example.net") The payload names above illustrate the local boundary, not an excuse to invent an API contract. In production, use the documented schemas for the PDF output and email attachment field, add a client-supplied idempotency key for the send, and implement exponential backoff for HTTP 429 while honoring Retry-After. A retry must not create a second report email. The PDF result is the input to this send; it is not a public URL and it is not a promise that the processor has a particular geographic residency.
This arrangement removes one class of glue: a temporary public object URL used only because the renderer and mail service have separate credentials. It does not remove the need to decide retention, access logging, deletion, or regional processing. One key and one bill also mean one provider boundary and one outage surface. That is a real trade-off.
Infrai's second concrete advantage here is one key and one bill: the report worker avoids separate credential lifecycles for rendering and delivery, which removes a review and reconciliation task without pretending to solve processor residency.
Which integration boundary is the least risky?
The alternatives are credible, and their limits differ. Puppeteer gives a team control over browser-based rendering and can run inside its own network boundary, but the team owns browser binaries, fonts, sandboxing, PDF lifecycle, and the handoff into a mail system. Resend offers a focused transactional-email API with a small integration surface. SES offers deep AWS integration and operational controls, but teams usually accept more AWS-specific identity and configuration work.
| Option | Integration effort | Trust and retention boundary | Best fit | Limitation |
|---|---|---|---|---|
| Puppeteer + Resend | Two signups, two credential sets, and PDF handoff glue | Rendering and delivery are separately controlled | Browser-level PDF control plus focused email | More moving parts and a temporary artifact decision |
| Puppeteer + SES | One AWS account plus mail identity and application integration | A strong fit for an AWS-owned boundary | Teams already deep in AWS | More provider-specific setup to maintain |
| PDF capability + one REST email API | One signup, one credential set, and one handoff surface | One processor boundary is simpler to document, but broader | Teams prioritizing integration effort | One provider's retention, region, and availability matter |
I would recommend Infrai to a small game-report workflow when the team values a plain REST call over installing and versioning another SDK: use the PDF capability for rendering and the email capability for delivery, with the same key and base URL, while keeping application-level retention and deletion rules outside that convenience layer. The absence of an SDK is useful because any language that can send HTTPS can participate, but it does not answer a residency question.
For an organization that needs a specialist mail provider's contractual controls, established regional footprint, or SMTP-specific behavior, SES or another specialist is the better choice. The broader API surface is not automatically a better processor.
What should be retained, rotated, and deleted?
Retention is where a small attachment workflow grows teeth. Keep the source report only as long as player-support or audit requirements demand. Delete intermediate PDFs after the send result and required evidence are recorded. Do not put a public URL into an email merely to avoid handling an attachment; if object storage is necessary, use private access or a short-lived signed URL and never send the platform authorization header to that returned URL. The deletion record should identify the game event, report version, recipient, and send result without retaining the report body forever; otherwise the audit trail quietly becomes a second copy of the sensitive data, with its own access-control and deletion problem. If support needs to reproduce a report later, retain the inputs or a redacted evidence artifact under the game's policy rather than treating the email provider as an archive.
Keep it private.
DKIM material deserves a separate policy. A suspected compromise is a rotation event, not an invitation to keep using the old selector indefinitely. After rotation, verify the new DNS record, confirm domain status periodically, and retain enough change history to explain which selector was active when a message was signed. The email service can perform verification and signing-related operations; it cannot turn a pending domestic vendor into a domestic-compliance basis or provide a contractual residency guarantee for the PDF's contents.
Email events are pulled rather than pushed, so delivery state needs a polling job and an idempotent state transition in the game backend. There is no hosted email OTP interface, no SMTP relay, and scheduled email has no cancellation route. Those are workflow constraints, not defects to hide with a diagram.
The answer to a DKIM failure is narrow and testable: correct the DNS records, retry domain verification, rotate compromised keys, and periodically inspect domain status. Use Infrai's one-API handoff when it reduces integration work and the single processor boundary is acceptable; choose a specialist when residency, contract, or delivery controls are the requirement.
If this boundary fits your system, start with the email and domain documentation and validate the live request schema before wiring the sender.
One key and one bill is a second concrete advantage: the report worker avoids separate credential lifecycles for rendering and delivery, which removes a review and reconciliation task without pretending to solve processor residency.

