fix: correct Resend DNS record type, name, and MX priority
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Three bugs in the notify provisioner DNS record upsert:
1. rec.Record ("DKIM"/"SPF") was used as the DNS record type — Cloudflare
doesn't know those labels. Fix: use rec.DNSType ("TXT"/"MX") from the
resendDNSRecord.type JSON field, which is the actual DNS record type.
2. rec.Name from Resend is already relative to the zone apex
(e.g., "resend._domainkey.mail.project-name"), not relative to the
registered domain. Code was doing rec.Name + "." + host which produced
a doubled subdomain. Fix: pass rec.Name directly — Cloudflare's
normalizeName appends ".baseDomain" to build the correct FQDN.
3. MX records have priority 10 in Resend's response but DNSRecord had no
Priority field and Cloudflare CreateRecord/UpdateRecord didn't send it.
Fix: add Priority int to domain.DNSRecord and include it in the body
for both Create and Update when non-zero.
These bugs caused DKIM/SPF DNS records to never be created for any project.
Re-provision affected projects using POST /projects/{id}/notify/provision
after clearing NOTIFY_RESEND_DOMAIN_ID from the credential store.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c54664b751
commit
ee1c214b7e
@ -55,6 +55,9 @@ func (c *Client) CreateRecord(ctx context.Context, record domain.DNSRecord) (*do
|
||||
"ttl": record.TTL,
|
||||
"proxied": record.Proxied,
|
||||
}
|
||||
if record.Priority > 0 {
|
||||
body["priority"] = record.Priority
|
||||
}
|
||||
|
||||
resp, err := c.doRequest(ctx, "POST", fmt.Sprintf("/zones/%s/dns_records", c.zoneID), body)
|
||||
if err != nil {
|
||||
@ -84,6 +87,9 @@ func (c *Client) UpdateRecord(ctx context.Context, recordID string, record domai
|
||||
"ttl": record.TTL,
|
||||
"proxied": record.Proxied,
|
||||
}
|
||||
if record.Priority > 0 {
|
||||
body["priority"] = record.Priority
|
||||
}
|
||||
|
||||
resp, err := c.doRequest(ctx, "PUT", fmt.Sprintf("/zones/%s/dns_records/%s", c.zoneID, recordID), body)
|
||||
if err != nil {
|
||||
|
||||
@ -129,21 +129,21 @@ func (p *Provisioner) CreateProjectNotify(ctx context.Context, projectID, slug s
|
||||
}
|
||||
|
||||
// 8. Add DNS records for DKIM/SPF (non-fatal).
|
||||
// Resend returns record names relative to the registered domain; build FQDNs for Cloudflare.
|
||||
// Cloudflare's normalizeName handles FQDNs ending in the zone name correctly.
|
||||
// rec.Name is relative to the zone apex (e.g., "resend._domainkey.mail.slug").
|
||||
// Cloudflare's normalizeName appends ".baseDomain" to build the FQDN.
|
||||
if p.dns != nil && len(dnsRecords) > 0 {
|
||||
for _, rec := range dnsRecords {
|
||||
fqdn := rec.Name + "." + host
|
||||
dnsRec := domain.DNSRecord{
|
||||
Type: rec.Record,
|
||||
Name: fqdn,
|
||||
Type: rec.DNSType,
|
||||
Name: rec.Name,
|
||||
Content: rec.Value,
|
||||
TTL: 1,
|
||||
Priority: rec.Priority,
|
||||
}
|
||||
if _, upsertErr := p.dns.UpsertRecord(ctx, dnsRec); upsertErr != nil {
|
||||
p.logger.Warn("failed to upsert notify DNS record",
|
||||
"name", fqdn,
|
||||
"record", rec.Record,
|
||||
"name", rec.Name,
|
||||
"type", rec.DNSType,
|
||||
"project_id", projectID,
|
||||
"error", upsertErr,
|
||||
)
|
||||
@ -341,18 +341,20 @@ func (p *Provisioner) ProvisionNotifyDomain(ctx context.Context, projectID, host
|
||||
p.logger.Info("resend domain created", "host", host, "domain_id", resendDomainID, "project_id", projectID)
|
||||
|
||||
// Step 8: Add DKIM/SPF DNS records (non-fatal).
|
||||
// rec.Name is relative to the zone apex (e.g., "resend._domainkey.mail.slug").
|
||||
// Cloudflare's normalizeName appends ".baseDomain" to build the FQDN.
|
||||
if p.dns != nil && len(dnsRecords) > 0 {
|
||||
for _, rec := range dnsRecords {
|
||||
fqdn := rec.Name + "." + host
|
||||
if _, upsertErr := p.dns.UpsertRecord(ctx, domain.DNSRecord{
|
||||
Type: rec.Record,
|
||||
Name: fqdn,
|
||||
Type: rec.DNSType,
|
||||
Name: rec.Name,
|
||||
Content: rec.Value,
|
||||
TTL: 1,
|
||||
Priority: rec.Priority,
|
||||
}); upsertErr != nil {
|
||||
p.logger.Warn("failed to upsert notify DNS record",
|
||||
"name", fqdn,
|
||||
"record", rec.Record,
|
||||
"name", rec.Name,
|
||||
"type", rec.DNSType,
|
||||
"project_id", projectID,
|
||||
"error", upsertErr,
|
||||
)
|
||||
|
||||
@ -28,12 +28,16 @@ type resendClient struct {
|
||||
}
|
||||
|
||||
// resendDNSRecord is a DNS record returned by Resend after domain creation.
|
||||
// The "record" JSON field contains the DNS record type (e.g., "TXT", "MX").
|
||||
// The "record" field is a Resend semantic label ("DKIM", "SPF").
|
||||
// The "type" field is the actual DNS record type ("TXT", "MX", "CNAME").
|
||||
// The "name" field is the subdomain relative to the zone apex (e.g., "resend._domainkey.mail.project-name"),
|
||||
// NOT relative to the registered domain — append "." + baseDomain to get the FQDN.
|
||||
type resendDNSRecord struct {
|
||||
Record string `json:"record"` // DNS record type: "TXT", "MX", "CNAME"
|
||||
Name string `json:"name"` // relative name (e.g., "resend._domainkey")
|
||||
Record string `json:"record"` // Resend semantic label: "DKIM", "SPF"
|
||||
DNSType string `json:"type"` // actual DNS type: "TXT", "MX", "CNAME"
|
||||
Name string `json:"name"` // subdomain relative to zone apex
|
||||
Value string `json:"value"` // record content
|
||||
Priority int `json:"priority,omitempty"`
|
||||
Priority int `json:"priority,omitempty"` // for MX records
|
||||
}
|
||||
|
||||
// resendCreateDomainResponse is the shape returned by POST /domains.
|
||||
|
||||
@ -4,11 +4,12 @@ package domain
|
||||
// DNSRecord represents a DNS record in a zone.
|
||||
type DNSRecord struct {
|
||||
ID string // Provider-specific ID
|
||||
Type string // A, AAAA, CNAME, TXT, etc.
|
||||
Type string // A, AAAA, CNAME, TXT, MX, etc.
|
||||
Name string // Subdomain or @ for root
|
||||
Content string // IP address or target
|
||||
TTL int // TTL in seconds, 1 = auto
|
||||
Proxied bool // Cloudflare proxy enabled
|
||||
Priority int // MX/SRV record priority (0 = not set)
|
||||
}
|
||||
|
||||
// DNSRecordType constants for common record types.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user