Skip to content

Commit 986270b

Browse files
committed
feat: mta-sts support
1 parent 29cae83 commit 986270b

File tree

7 files changed

+110
-1
lines changed

7 files changed

+110
-1
lines changed

.env.local.example

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
# ************************************ platform ********************************************
1616
############################################################################################
1717
PLATFORM_URL=http://localhost:3300
18+
# Generate by running in a terminal: openssl rand -hex 32
19+
PLATFORM_SECRET=secretsecretsecret
1820

1921

2022

apps/mail-bridge/postal-db/functions.ts

+69-1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,23 @@ export type GetDomainDNSRecordsOutput =
119119
optimal: string;
120120
acceptable: string;
121121
};
122+
mtaSts: {
123+
dns: {
124+
valid: boolean;
125+
name: string;
126+
value: string;
127+
};
128+
tls: {
129+
valid: boolean;
130+
name: string;
131+
value: string;
132+
};
133+
policy: {
134+
valid: boolean;
135+
name: string;
136+
value: string;
137+
};
138+
};
122139
}
123140
| { error: string };
124141

@@ -168,6 +185,23 @@ export async function getDomainDNSRecords(
168185
name: '',
169186
optimal: '',
170187
acceptable: ''
188+
},
189+
mtaSts: {
190+
dns: {
191+
valid: false,
192+
name: '',
193+
value: ''
194+
},
195+
tls: {
196+
valid: false,
197+
name: '',
198+
value: ''
199+
},
200+
policy: {
201+
valid: false,
202+
name: '',
203+
value: ''
204+
}
171205
}
172206
};
173207

@@ -313,7 +347,7 @@ export async function getDomainDNSRecords(
313347
}
314348
records.mx.name = domainInfo.name;
315349
records.mx.priority = 1;
316-
records.mx.value = `mx.${postalServerUrl}`;
350+
records.mx.value = `${postalServerUrl}`;
317351
records.mx.valid = true;
318352

319353
if (domainInfo.mxStatus !== 'OK' || forceReverify) {
@@ -356,6 +390,40 @@ export async function getDomainDNSRecords(
356390
records.dmarc.name = '_dmarc';
357391
records.dmarc.optimal = buildDmarcRecord({ p: 'reject' });
358392
records.dmarc.acceptable = buildDmarcRecord({ p: 'quarantine' });
393+
394+
const mtaStsDnsRecord = await lookupTXT(`_mta-sts.${domainInfo.name}`);
395+
records.mtaSts.dns.name = '_mta-sts';
396+
records.mtaSts.dns.value = `v=STSv1; id=${Date.now()}`;
397+
if (mtaStsDnsRecord.success && mtaStsDnsRecord.data.length > 0) {
398+
records.mtaSts.dns.valid =
399+
mtaStsDnsRecord.data.filter(
400+
(_) => _.startsWith('v=STSv1;') && _.includes('id=')
401+
).length === 1;
402+
}
403+
404+
const mtaStsTlsRecord = await lookupTXT(`_smtp._tls.${domainInfo.name}`);
405+
records.mtaSts.tls.name = '_smtp._tls';
406+
records.mtaSts.tls.value = `v=TLSRPTv1; rua=mailto:[email protected]`;
407+
if (mtaStsTlsRecord.success && mtaStsTlsRecord.data.length > 0) {
408+
records.mtaSts.tls.valid =
409+
mtaStsTlsRecord.data.filter(
410+
(_) =>
411+
_.startsWith('v=TLSRPTv1;') &&
412+
_.includes('rua=') &&
413+
_.includes('mailto:[email protected]')
414+
).length === 1;
415+
}
416+
417+
const mtaStsPolicyRecord = await lookupCNAME(`mta-sts.${domainInfo.name}`);
418+
records.mtaSts.policy.name = 'mta-sts';
419+
records.mtaSts.policy.value = `mta-sts.${postalServerUrl}`;
420+
if (
421+
mtaStsPolicyRecord.success &&
422+
mtaStsPolicyRecord.data.includes(records.mtaSts.policy.value)
423+
) {
424+
records.mtaSts.policy.valid = true;
425+
}
426+
359427
return records;
360428
}
361429

apps/mail-bridge/trpc/routers/domainRouter.ts

+17
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,23 @@ export const domainRouter = router({
178178
name: 'localhost',
179179
acceptable: 'v=DMARC1; p=quarantine;',
180180
optimal: 'v=DMARC1; p=reject;'
181+
},
182+
mtaSts: {
183+
dns: {
184+
valid: true,
185+
name: 'localhost',
186+
value: 'v=STSv1; id=123456789'
187+
},
188+
tls: {
189+
valid: true,
190+
name: 'localhost',
191+
value: 'v=TLSRPTv1; rua=mailto:tlsrpt@localhost'
192+
},
193+
policy: {
194+
valid: true,
195+
name: 'localhost',
196+
value: 'mta-sts.localhost'
197+
}
181198
}
182199
} satisfies GetDomainDNSRecordsOutput;
183200
}

apps/platform/routes/services.ts

+2
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,5 @@ servicesApi.post(
2525
return c.json({ results });
2626
}
2727
);
28+
29+
servicesApi.post();

packages/caddy-mta-sts/Caddyfile

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
on_demand_tls {
3+
ask {env.PLATFORM_URL}/caddy-check/{env.PLATFORM_SECRET}
4+
}
5+
}
6+
7+
https:// {
8+
tls {
9+
on_demand
10+
}
11+
root * ./files
12+
file_server
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
version: STSv1
2+
mode: enforce
3+
mx: *.e.uninbox.com
4+
max_age: 86400

packages/database/schema.ts

+3
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,9 @@ export const domains = mysqlTable(
568568
returnPathDnsValid: boolean('return_path_dns_valid')
569569
.notNull()
570570
.default(false),
571+
mtaStsDnsValid: boolean('mta_sts_dns_valid').notNull().default(false),
572+
mtaStsTlsValid: boolean('mta_sts_tls_valid').notNull().default(false),
573+
mtaStsPolicyValid: boolean('mta_sts_policy_valid').notNull().default(false),
571574
lastDnsCheckAt: timestamp('last_dns_check_at'),
572575
disabledAt: timestamp('disabled_at'),
573576
verifiedAt: timestamp('verified_at'),

0 commit comments

Comments
 (0)