When building SaaS applications with custom subdomains, developers often face DNS configuration limitations. While wildcard A records (*.example.com
) work perfectly for web traffic, the same approach fails for MX records due to RFC standards and security considerations.
The DNS RFC 1034 specifically states that wildcard MX records can create significant email delivery problems. This is because:
- MX records require explicit verification for anti-spam measures
- Wildcards can mask invalid domains in email routing
- Major email providers may reject mail from wildcard domains
Here are three proven approaches to solve this:
1. API-Driven DNS Management
Use your cloud provider's API to automatically create records:
# Example using AWS Route53 API
import boto3
def create_mx_record(subdomain):
client = boto3.client('route53')
response = client.change_resource_record_sets(
HostedZoneId='Z1PA6795UKMFR9',
ChangeBatch={
'Changes': [{
'Action': 'UPSERT',
'ResourceRecordSet': {
'Name': f'{subdomain}.myapp.example',
'Type': 'MX',
'TTL': 300,
'ResourceRecords': [
{'Value': '10 mx.sendgrid.net'}
]
}
}]
}
)
return response
2. CNAME Flattening Technique
Some DNS providers support CNAME flattening which can be combined with MX records:
# DNS configuration example
whisky.myapp.example. IN CNAME sendgrid.mail.myapp.example.
sendgrid.mail.myapp.example. IN MX 10 mx.sendgrid.net
3. Subdomain Delegation
Delegate the subdomain to SendGrid's DNS:
# NS record for delegation
*.myapp.example. IN NS ns1.sendgrid.com.
*.myapp.example. IN NS ns2.sendgrid.com.
When choosing a solution, consider:
- Email deliverability metrics
- DNS propagation times
- Provider-specific limitations
- Automation complexity
Implement monitoring for your DNS configuration:
// Example using Node.js to verify DNS records
const dns = require('dns');
async function verifyMX(subdomain) {
return new Promise((resolve) => {
dns.resolveMx(${subdomain}.myapp.example, (err, addresses) => {
if (err) resolve(false);
resolve(addresses.some(mx => mx.exchange.includes('sendgrid')));
});
});
}
When implementing multi-tenant SaaS applications with custom subdomains, developers often need to handle both web traffic (A records) and email delivery (MX records) through these subdomains. While wildcard A records are standard practice, MX records present unique challenges due to email delivery requirements and DNS specifications.
The DNS RFC standards (specifically RFC 1034 and RFC 2181) technically allow wildcard MX records, but many DNS providers restrict this due to:
- Email delivery reliability concerns
- Potential for spam and phishing abuse
- Interoperability issues with some mail servers
Here are three viable approaches to solve this:
1. Automated DNS Record Creation
Implement an API-driven solution that creates individual MX records when users sign up:
// Example using AWS Route53 API (Node.js)
const AWS = require('aws-sdk');
const route53 = new AWS.Route53();
async function createMXRecord(subdomain) {
const params = {
HostedZoneId: 'YOUR_ZONE_ID',
ChangeBatch: {
Changes: [
{
Action: 'CREATE',
ResourceRecordSet: {
Name: ${subdomain}.myapp.example,
Type: 'MX',
TTL: 300,
ResourceRecords: [
{ Value: '10 mx.sendgrid.net' }
]
}
}
]
}
};
return route53.changeResourceRecordSets(params).promise();
}
2. CNAME Redirection Alternative
For SendGrid specifically, you can use their domain verification approach:
whisky.myapp.example. CNAME sendgrid.net.
This requires SendGrid's domain authentication setup but avoids MX records entirely.
3. Wildcard SPF Record
If you only need to send email (not receive), configure a wildcard SPF record:
*.myapp.example. TXT "v=spf1 include:sendgrid.net -all"
- DNS propagation delays (typically 5-60 minutes)
- TTL (Time-To-Live) settings for faster updates
- API rate limits from your DNS provider
- Monitoring for DNS record creation failures
Here's a complete solution using AWS services:
// Lambda function for automated subdomain provisioning
exports.handler = async (event) => {
const { subdomain } = event;
// 1. Create A record
await route53.changeResourceRecordSets({
HostedZoneId: process.env.HOSTED_ZONE_ID,
ChangeBatch: {
Changes: [
{
Action: 'CREATE',
ResourceRecordSet: {
Name: ${subdomain}.myapp.example,
Type: 'A',
TTL: 60,
ResourceRecords: [{ Value: '192.0.2.123' }]
}
},
{
Action: 'CREATE',
ResourceRecordSet: {
Name: ${subdomain}.myapp.example,
Type: 'MX',
TTL: 300,
ResourceRecords: [
{ Value: '10 mx1.sendgrid.net' },
{ Value: '20 mx2.sendgrid.net' }
]
}
}
]
}
}).promise();
// 2. Configure SendGrid
const sgResponse = await axios.post('https://api.sendgrid.com/v3/whitelabel/domains', {
domain: ${subdomain}.myapp.example,
subdomain,
username: 'api',
ips: [],
custom_spf: true,
default: false
}, {
headers: { Authorization: Bearer ${process.env.SENDGRID_KEY} }
});
return { success: true, data: sgResponse.data };
};
Remember to implement proper error handling and retry logic in production code.