287 lines
9.2 KiB
Markdown
287 lines
9.2 KiB
Markdown
[KUBE](../README.md) / Mail server
|
|
|
|
# Mail server
|
|
|
|
## Template of zone
|
|
```zone
|
|
$TTL 300
|
|
@ IN SOA ns1.mydns.example. dnsadmin.mydomain.com. (
|
|
2025091001 ; serial
|
|
3600 ; refresh
|
|
900 ; retry
|
|
1209600 ; expire
|
|
300 ; minimum
|
|
)
|
|
IN NS ns1.mydns.example.
|
|
IN NS ns2.mydns.example.
|
|
|
|
; ===== A/AAAA =====
|
|
mta IN A {{PUBLIC_IPV4}}
|
|
; mta IN AAAA {{PUBLIC_IPV6}} ; if available
|
|
|
|
; if desired, client sites can resolve to the same IP
|
|
{{US1}} IN A {{PUBLIC_IPV4}}
|
|
{{US2}} IN A {{PUBLIC_IPV4}}
|
|
{{US3}} IN A {{PUBLIC_IPV4}}
|
|
|
|
; ===== MX =====
|
|
; @ IN MX 10 mta.{{ROOT_DOMAIN}}. ; the root domain also accepts mail, if needed
|
|
{{US1}} IN MX 10 mta.{{ROOT_DOMAIN}}.
|
|
{{US2}} IN MX 10 mta.{{ROOT_DOMAIN}}.
|
|
{{US3}} IN MX 10 mta.{{ROOT_DOMAIN}}.
|
|
|
|
; ===== SPF =====
|
|
; @ IN TXT "v=spf1 mx ~all" ; if available
|
|
mta IN TXT "v=spf1 a -all"
|
|
{{US1}} IN TXT "v=spf1 mx ~all"
|
|
{{US2}} IN TXT "v=spf1 mx ~all"
|
|
{{US3}} IN TXT "v=spf1 mx ~all"
|
|
|
|
; ===== DKIM =====
|
|
; For each customer domain, publish its own public key.
|
|
; The selector can be shared (e.g., selector1); keys are different.
|
|
selector1._domainkey.{{US1}} IN TXT "v=DKIM1; k=rsa; p={{PUBKEY_US1}}"
|
|
selector1._domainkey.{{US2}} IN TXT "v=DKIM1; k=rsa; p={{PUBKEY_US2}}"
|
|
selector1._domainkey.{{US3}} IN TXT "v=DKIM1; k=rsa; p={{PUBKEY_US3}}"
|
|
|
|
; ===== DMARC =====
|
|
; Initially p=none to collect reports without impacting delivery.
|
|
_dmarc.{{US1}} IN TXT "v=DMARC1; p=none; adkim=r; aspf=r; rua=mailto:{{DMARC_AGG}}; ruf=mailto:{{DMARC_FOR}}"
|
|
_dmarc.{{US2}} IN TXT "v=DMARC1; p=none; adkim=r; aspf=r; rua=mailto:{{DMARC_AGG}}; ruf=mailto:{{DMARC_FOR}}"
|
|
_dmarc.{{US3}} IN TXT "v=DMARC1; p=none; adkim=r; aspf=r; rua=mailto:{{DMARC_AGG}}; ruf=mailto:{{DMARC_FOR}}"
|
|
; It is recommended to set DMARC on the root domain to cover ALL subdomains with a single policy.
|
|
; _dmarc IN TXT "v=DMARC1; p=quarantine; sp=quarantine; adkim=r; aspf=r; rua=mailto:{{DMARC_AGG}}; ruf=mailto:{{DMARC_FOR}}"
|
|
; (if test mode first — replace p=none, sp=none)
|
|
|
|
; ===== Additionally (optional but useful) =====
|
|
; MTA-STS (if to implement)
|
|
;_mta-sts IN TXT "v=STSv1; id=2025-09-10"
|
|
;_smtp._tls IN TXT "v=TLSRPTv1; rua=mailto:tlsrpt@{{ROOT_DOMAIN}}"
|
|
;autoconfig IN CNAME autoconfig.mailhost.example. ; for client autoconfiguration
|
|
;autodiscover IN CNAME autodiscover.mailhost.example.
|
|
```
|
|
|
|
```zone
|
|
; ===== PTR =====
|
|
{{PUBLIC_IPV4}} → mta.{{ROOT_DOMAIN}}
|
|
```
|
|
|
|
Check: Postfix HELO/EHLO = mta.`{{ROOT_DOMAIN}}`
|
|
|
|
## Example
|
|
`{{ROOT_DOMAIN}}` → krumax.cz \
|
|
`{{PUBLIC_IPV4}}` → 31.31.73.67 \
|
|
`{{US1}}`/`{{US2}}`/`{{US3}}` → us1 / us2 / us3 \
|
|
`{{PUBKEY_US1}}`/`{{PUBKEY_US2}}`/`{{PUBKEY_US3}}` → DKIM public keys (only the content after `p=`, in a single line) \
|
|
`{{DMARC_AGG}}`/`{{DMARC_FOR}}` → Addresses for DMARC reports (for example, dmarc@krumax.cz)
|
|
|
|
```zone
|
|
$TTL 300
|
|
|
|
; ===== A =====
|
|
mta IN A 31.31.73.67
|
|
us1 IN A 31.31.73.67
|
|
us2 IN A 31.31.73.67
|
|
us3 IN A 31.31.73.67
|
|
|
|
; ===== MX =====
|
|
us1 IN MX 10 mta.krumax.cz.
|
|
us2 IN MX 10 mta.krumax.cz.
|
|
us3 IN MX 10 mta.krumax.cz.
|
|
|
|
; ===== SPF =====
|
|
mta IN TXT "v=spf1 a -all"
|
|
us1 IN TXT "v=spf1 mx ~all"
|
|
us2 IN TXT "v=spf1 mx ~all"
|
|
us3 IN TXT "v=spf1 mx ~all"
|
|
|
|
; ===== DKIM =====
|
|
selector1._domainkey.us1 IN TXT "v=DKIM1; k=rsa; p=MIIBI...(us1)"
|
|
selector1._domainkey.us2 IN TXT "v=DKIM1; k=rsa; p=MIIBI...(us2)"
|
|
selector1._domainkey.us3 IN TXT "v=DKIM1; k=rsa; p=MIIBI...(us3)"
|
|
|
|
; ===== DMARC =====
|
|
_dmarc.us1 IN TXT "v=DMARC1; p=none; adkim=r; aspf=r; rua=mailto:dmarc@krumax.cz; ruf=mailto:dmarc@krumax.cz"
|
|
_dmarc.us2 IN TXT "v=DMARC1; p=none; adkim=r; aspf=r; rua=mailto:dmarc@krumax.cz; ruf=mailto:dmarc@krumax.cz"
|
|
_dmarc.us3 IN TXT "v=DMARC1; p=none; adkim=r; aspf=r; rua=mailto:dmarc@krumax.cz; ruf=mailto:dmarc@krumax.cz"
|
|
```
|
|
|
|
```zone
|
|
; ===== PTR =====
|
|
31.31.73.67 → mta.krumax.cz
|
|
```
|
|
|
|
|
|
## Example of adding a new domain in Postfix
|
|
1. Adding the domain and generating DKIM
|
|
```bash
|
|
sudo kube.sh postfix add us2.krumax.cz
|
|
```
|
|
2. Retrieving DKIM
|
|
```bash
|
|
sudo kube.sh postfix dkim us2.krumax.cz
|
|
```
|
|
3. Adding DNS records:
|
|
```zone
|
|
us2 IN A 31.31.73.67
|
|
us2 IN MX 10 mta.krumax.cz.
|
|
selector1._domainkey.us2 IN TXT "v=DKIM1; k=rsa; p=MIIBI...(from step 2)"
|
|
_dmarc.us2 IN TXT "v=DMARC1; p=none; adkim=r; aspf=r; rua=mailto:dmarc@krumax.cz; ruf=mailto:dmarc@krumax.cz"
|
|
```
|
|
|
|
|
|
## Send mail in PHP.
|
|
Create file for test send mail `send-mail.php`
|
|
```php
|
|
<?
|
|
mb_internal_encoding('UTF-8');
|
|
$to = 'maksym.krugol@wedos.org';
|
|
$toName = 'Maksym Krugol';
|
|
$from = 'no-reply@us1.krumax.cz';
|
|
$fromName = 'Support team US1';
|
|
$return = 'bounce@us1.krumax.cz';
|
|
$subject = mb_encode_mimeheader('Test delivery (PHP)', 'UTF-8', 'B', "\r\n");
|
|
$bodyText = "Hi! Test with PHP.";
|
|
$bodyQP = quoted_printable_encode($bodyText);
|
|
$headers = [
|
|
"From: $fromName <$from>",
|
|
"Reply-To: $from",
|
|
"Return-Path: $return",
|
|
"Date: " . date('r'),
|
|
"Message-ID: <" . time() . "." . bin2hex(random_bytes(6)) . "@" . $hostname . ">",
|
|
"MIME-Version: 1.0",
|
|
"Content-Type: text/plain; charset=UTF-8",
|
|
"Content-Transfer-Encoding: quoted-printable",
|
|
"List-Unsubscribe: <mailto:unsubscribe@us1.krumax.cz?subject=unsubscribe>, <https://us1.krumax.cz/unsubscribe/".rawurlencode('maksym.krugol@wedos.org').">",
|
|
];
|
|
$headersStr = implode("\r\n", $headers);
|
|
|
|
$status = mail("$toName <$to>", $subject, $bodyQP, $headersStr, "-f$return");
|
|
echo $status ? "Success\n" : "Failed\n";
|
|
```
|
|
|
|
|
|
## Send mail in Wordpress.
|
|
1. Add Wordpress plugin `/wp-content/mu-plugins/smtp.php`
|
|
```php
|
|
<?php
|
|
// For 25 port (without TLS/login)
|
|
add_action('phpmailer_init', function ($phpmailer) {
|
|
$phpmailer->isSMTP();
|
|
$phpmailer->XMailer = 'krumaxMailer 1.0';
|
|
$phpmailer->Host = 'mta.krumax.cz';
|
|
$phpmailer->Port = 25;
|
|
$phpmailer->SMTPAuth = false;
|
|
$phpmailer->SMTPSecure = '';
|
|
$phpmailer->SMTPAutoTLS = false;
|
|
$phpmailer->From = 'no-reply@us1.krumax.cz';
|
|
// $phpmailer->FromName = 'Support team US1';
|
|
// $phpmailer->Hostname = 'us1.krumax.cz';
|
|
// $phpmailer->Encoding = 'quoted-printable';
|
|
// $phpmailer->Sender = 'bounce@us1.krumax.cz';
|
|
// $phpmailer->Timeout = 15;
|
|
});
|
|
```
|
|
```php
|
|
<?php
|
|
// For 587 port (with TLS/login)
|
|
add_action('phpmailer_init', function ($phpmailer) {
|
|
$phpmailer->isSMTP();
|
|
$phpmailer->XMailer = 'krumaxMailer 1.0';
|
|
$phpmailer->Host = 'mta.krumax.cz';
|
|
$phpmailer->Port = 587;
|
|
$phpmailer->Username = 'postmaster@us1.krumax.cz';
|
|
$phpmailer->Password = 'qwerty';
|
|
$phpmailer->SMTPAuth = true;
|
|
$phpmailer->SMTPSecure = 'tls';
|
|
$phpmailer->SMTPAutoTLS = true;
|
|
$phpmailer->SMTPOptions = [
|
|
'ssl' => [
|
|
'allow_self_signed' => true,
|
|
],
|
|
];
|
|
$phpmailer->From = 'no-reply@us1.krumax.cz';
|
|
// $phpmailer->FromName = 'Support team US1';
|
|
// $phpmailer->Hostname = 'us1.krumax.cz';
|
|
// $phpmailer->Encoding = 'quoted-printable';
|
|
// $phpmailer->Sender = 'bounce@us1.krumax.cz';
|
|
// $phpmailer->Timeout = 15;
|
|
});
|
|
```
|
|
2. Create file for test send mail `/send-mail.php`
|
|
```php
|
|
<?php
|
|
require_once 'wp-load.php';
|
|
|
|
$domain = "us1.krumax.cz";
|
|
$to = "maksym.krugol@wedos.org";
|
|
$toName = "Maksym Krugol";
|
|
$subject = "Test delivery (Wordpress)";
|
|
$message = "Hi! Test with Wordpress.";
|
|
$headers = [
|
|
"List-Unsubscribe: <mailto:unsubscribe@{$domain}?subject=unsubscribe>, <https://{$domain}/unsubscribe/{$to}>"
|
|
];
|
|
|
|
if (wp_mail("$toName <{$to}>", $subject, $message, $headers)) {
|
|
echo "Success";
|
|
} else {
|
|
echo "Failed";
|
|
}
|
|
```
|
|
3. Open URL http://us1.krumax.cz/send-mail.php
|
|
|
|
|
|
## Send mail from pod in k3s (use Postfix).
|
|
1. Install postfix: `apt-get install -y postfix`
|
|
2. Set config:
|
|
```bash
|
|
postconf -e 'myhostname = mta.krumax.cz'
|
|
postconf -e 'mydomain = us1.krumax.cz'
|
|
postconf -e 'myorigin = $mydomain'
|
|
```
|
|
3. Create file `test.mail`:
|
|
```
|
|
From: Support team US1 <no-reply@us1.krumax.cz>
|
|
To: Maksym Krugol <maksym.krugol@wedos.org>
|
|
Subject: Test delivery (postfix)
|
|
Date: Wed, 17 Sep 2025 10:53:13 +0200
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset=UTF-8
|
|
Content-Transfer-Encoding: quoted-printable
|
|
|
|
Hi! Test with /usr/sbin/sendmail.
|
|
```
|
|
4. Send mail: `sendmail -v -oi -t -f 'no-reply@us1.krumax.cz' < test.mail`
|
|
|
|
## Send mail from pod in k3s (use msmtp).
|
|
1. Install msmtp: `apt-get install -y msmtp msmtp-mta ca-certificates`
|
|
2. Set config `/etc/msmtprc`:
|
|
```
|
|
defaults
|
|
auth on
|
|
tls on
|
|
tls_starttls on
|
|
tls_trust_file /etc/ssl/certs/ca-certificates.crt
|
|
logfile /var/log/msmtp.log
|
|
|
|
account default
|
|
host mta.krumax.cz
|
|
port 587
|
|
from no-reply@us1.krumax.cz
|
|
user postmaster@us1.krumax.cz
|
|
password qwerty
|
|
```
|
|
3. Create file `test.mail`:
|
|
```
|
|
From: Support team US1 <no-reply@us1.krumax.cz>
|
|
To: Maksym Krugol <maksym.krugol@wedos.org>
|
|
Subject: Test delivery (msmtp)
|
|
Date: Wed, 17 Sep 2025 10:53:13 +0200
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset=UTF-8
|
|
Content-Transfer-Encoding: quoted-printable
|
|
|
|
Hi! Test with msmtp/sendmail.
|
|
```
|
|
4. Send mail: `sendmail -v -oi -t -f 'no-reply@us1.krumax.cz' < test.mail`
|