added new StepList component
This commit is contained in:
parent
f5d2557a79
commit
24cc461fb7
3 changed files with 346 additions and 17 deletions
95
src/components/StepList.astro
Normal file
95
src/components/StepList.astro
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
---
|
||||||
|
interface Step {
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { steps, currentStep } = Astro.props as {
|
||||||
|
steps: Step[];
|
||||||
|
currentStep: number;
|
||||||
|
};
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="step-box">
|
||||||
|
<ol class="steps">
|
||||||
|
{
|
||||||
|
steps.map((step, i) => (
|
||||||
|
<li class="step">
|
||||||
|
<div class={`circle ${i < currentStep ? "done" : i === currentStep ? "current" : "todo"}`}>{i + 1}</div>
|
||||||
|
<p class="label">{step.title}</p>
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.step-box {
|
||||||
|
background: #f9fafb; /* light gray */
|
||||||
|
border: 1px solid #e5e7eb; /* gray border */
|
||||||
|
border-radius: 0.75rem; /* rounded corners */
|
||||||
|
padding: 1.5rem;
|
||||||
|
max-width: 400px; /* optional, keeps it compact */
|
||||||
|
}
|
||||||
|
|
||||||
|
.steps {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
width: 1.6rem;
|
||||||
|
height: 1.6rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid gray;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: bold;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle.done {
|
||||||
|
background: #22c55e; /* green */
|
||||||
|
border-color: #22c55e;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle.current {
|
||||||
|
background: #3b82f6; /* blue */
|
||||||
|
border-color: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
animation: pulse 1.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle.todo {
|
||||||
|
border-color: #9ca3af; /* gray */
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #374151; /* gray-700 */
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.6);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
box-shadow: 0 0 0 6px rgba(59, 130, 246, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
245
src/content/docs/ispmail-trixie/160-receive-emails-postfix.mdx
Normal file
245
src/content/docs/ispmail-trixie/160-receive-emails-postfix.mdx
Normal file
|
|
@ -0,0 +1,245 @@
|
||||||
|
---
|
||||||
|
title: "Receive emails: Postfix"
|
||||||
|
lastUpdated: 2025-08-20
|
||||||
|
slug: ispmail-trixie/receive-emails-postfix
|
||||||
|
sidebar:
|
||||||
|
order: 160
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Aside } from "@astrojs/starlight/components";
|
||||||
|
|
||||||
|
import "@splidejs/splide/css";
|
||||||
|
import { Splide, SplideSlide } from "astro-splide";
|
||||||
|
|
||||||
|
import { Steps } from "@astrojs/starlight/components";
|
||||||
|
|
||||||
|
import Box from "../../../components/Box.astro";
|
||||||
|
|
||||||
|
import StepList from "../../../components/StepList.astro";
|
||||||
|
|
||||||
|
<StepList
|
||||||
|
steps={[
|
||||||
|
{ title: "Install Astro", description: "Set up your project." },
|
||||||
|
{ title: "Add Starlight", description: "Install the theme." },
|
||||||
|
{ title: "Customize", description: "Adjust settings to your needs." },
|
||||||
|
{ title: "Deploy", description: "Publish to production." },
|
||||||
|
]}
|
||||||
|
currentStep={2}
|
||||||
|
/>
|
||||||
|
|
||||||
|
I have created a slideshow to help you understand the process of receiving an email from a remote mail server. Use the
|
||||||
|
arrows to navigate between slides:
|
||||||
|
|
||||||
|
<Splide>
|
||||||
|
{Array.from({ length: 24 }, (_, i) => i + 1).map((i) => (
|
||||||
|
<SplideSlide key={i}>
|
||||||
|
<img src={`/big-picture-receive/${String(i).padStart(2, "0")}.svg`} alt={`Slide ${i}`} />
|
||||||
|
</SplideSlide>
|
||||||
|
))}
|
||||||
|
</Splide>
|
||||||
|
|
||||||
|
## DNS
|
||||||
|
|
||||||
|
<ul class="steps steps-vertical lg:steps-horizontal">
|
||||||
|
<li class="step step-primary">Register</li>
|
||||||
|
<li class="step step-primary">Choose plan</li>
|
||||||
|
<li class="step">Purchase</li>
|
||||||
|
<li class="step">Receive Product</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<Box icon='check-list'>
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
1. **DNS is used to find your mail server. First the MX record. Then the A and/or AAAA record.**
|
||||||
|
1. Postfix receives emails using SMTP (the Simple Mail Transport Protocol)
|
||||||
|
1. MariaDB stores information about your domains and mail users
|
||||||
|
1. rspamd checks if it is spam (optional)
|
||||||
|
1. Dovecot saves it to disk
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
Let's begin with setting up the DNS records. Say that you want to receive emails for the `example.com` domain. If some
|
||||||
|
other mail server on the internet wants to send an email to your server, it looks for two records:
|
||||||
|
|
||||||
|
- **MX record (`example.com`)**
|
||||||
|
The MX (Mail Exchange) record tells other mail servers _where to deliver emails_ for the domain `example.com`.
|
||||||
|
Instead of pointing directly to an IP address, it points to a **hostname** (for example, `smtp.example.com`).
|
||||||
|
MX records also include a **priority value**. If multiple mail servers are listed, the one with the lowest priority
|
||||||
|
number is tried first. Others are used as fallback if the first is unavailable.
|
||||||
|
|
||||||
|
- **A / AAAA record (`smtp.example.com`)**
|
||||||
|
The hostname specified in the MX record (`smtp.example.com`) must resolve to an IP address so that other mail servers
|
||||||
|
know how to reach it.
|
||||||
|
- An **A record** maps the hostname to an IPv4 address.
|
||||||
|
- An **AAAA record** maps the hostname to an IPv6 address.
|
||||||
|
|
||||||
|
Once resolved, the sending mail server connects to that IP address on **TCP port 25**, which is the standard port for
|
||||||
|
email delivery (SMTP).
|
||||||
|
|
||||||
|
<Aside>
|
||||||
|
|
||||||
|
If no MX record is found, the remote mail server will instead directly look for an A or AAAA record of `example.com`.
|
||||||
|
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
<Aside type="caution">
|
||||||
|
|
||||||
|
The MX record must point to a hostname. It **must not** point to an IP address directly.
|
||||||
|
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
In zone syntax you would create something like:
|
||||||
|
|
||||||
|
```
|
||||||
|
@ IN MX 10 smtp
|
||||||
|
smtp IN A 100.64.17.3
|
||||||
|
smtp IN A fd7a:115c:a1e0::17
|
||||||
|
```
|
||||||
|
|
||||||
|
If possible, also add a PTR record that makes the IP addresses point back to the name `smtp.example.com`. Other mail
|
||||||
|
servers may be more likely so flag your sent emails as spam if you do not have a PTR record. Preferable the forward
|
||||||
|
(A/AAAA) and reverse (PTR) records match.
|
||||||
|
|
||||||
|
<div class="scroll-trigger" data-slide="5">
|
||||||
|
dingdong
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## Postfix
|
||||||
|
|
||||||
|
Now that other mail servers can locate your mail server, the next step is to make sure it can receive and process
|
||||||
|
incoming emails. This task is handled by Postfix, which communicates using the SMTP protocol.
|
||||||
|
|
||||||
|
Postfix knows if an email address is valid by checking for…
|
||||||
|
|
||||||
|
- Virtual Domains (am I responsible for this domain?)
|
||||||
|
- Virtual Aliases (do I have to redirect this address to another address?)
|
||||||
|
- Virtual Mailboxes (do I have a valid mailbox to store the email to?)
|
||||||
|
|
||||||
|
### Virtual Domains
|
||||||
|
|
||||||
|
There are different ways for Postfix to get that information. From text files, LDAP, MongoDB or PostgreSQL. Or via
|
||||||
|
MariaDB – which is what we will use. But whatever kind of data source you configure, Postfix needs the information as a
|
||||||
|
_mapping_. Consider it as a _question_ and an _answer_.
|
||||||
|
|
||||||
|
- Question: is `example.org` one of our virtual domains?
|
||||||
|
- Answer: yes
|
||||||
|
|
||||||
|
If the information is **not** available you just get no answer:
|
||||||
|
|
||||||
|
- Question: is `example.net` one of our virtual domains?
|
||||||
|
- Answer: (no answer)
|
||||||
|
|
||||||
|
So Postfix hast to ask MariaDB that question:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT "yes" FROM virtual_domains WHERE name='example.net'
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the following code in your shell to create a configuration file creating that mapping:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cat > /etc/postfix/virtual-mailbox-domains.cf << EOF
|
||||||
|
user = mailserver
|
||||||
|
password = SECOND-PASSWORD-HERE
|
||||||
|
hosts = 127.0.0.1
|
||||||
|
dbname = mailserver
|
||||||
|
query = SELECT 1 FROM virtual_domains WHERE name='%s'
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
This has created a configuration file called `/etc/postfix/virtual-mailbox-domains.cf`. The `user`, `password`, `hosts`
|
||||||
|
and `dbname` definitions tell Postfix how to connect to the database. And the `query` asks the _question_ if a certain
|
||||||
|
domain is present in the database table `virtual_domains`. Using `SELECT 1` we return just the number 1 if such an entry
|
||||||
|
was found. In fact it does not matter what we return. Any _answer_ is fine.
|
||||||
|
|
||||||
|
Don't forget to replace the `SECOND-PASSWORD-HERE` by your own password for the `mailserver` user.
|
||||||
|
|
||||||
|
### Virtual Aliases
|
||||||
|
|
||||||
|
Now let's deal with aliases. As shown earlier an alias redirects an email to another email address. So the _question_
|
||||||
|
and _answer_ game goes like this:
|
||||||
|
|
||||||
|
- Question: is there an alias for `jack@example.org`?
|
||||||
|
- Answer: `john@example.org`
|
||||||
|
|
||||||
|
Postfix would then check if there are further aliases:
|
||||||
|
|
||||||
|
- Question: is there an alias for `john@example.org`?
|
||||||
|
- Answer: (no answer)
|
||||||
|
|
||||||
|
Apparently, there aren't any. So the email will go to `john@example.org`.
|
||||||
|
|
||||||
|
Run this code to create the appropriate mapping file:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cat > /etc/postfix/virtual-alias-maps.cf << EOF
|
||||||
|
user = mailserver
|
||||||
|
password = SECOND-PASSWORD-HERE
|
||||||
|
hosts = 127.0.0.1
|
||||||
|
dbname = mailserver
|
||||||
|
query = SELECT destination FROM virtual_aliases WHERE source='%s'
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
The query now gets all the `destination` email addresses from the database for a certain `source` email address.
|
||||||
|
|
||||||
|
### Virtual Mailboxes
|
||||||
|
|
||||||
|
The last mapping we need is a query to find valid mailboxes. Without further ado:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cat > /etc/postfix/virtual-mailbox-maps.cf << EOF
|
||||||
|
user = mailserver
|
||||||
|
password = SECOND-PASSWORD-HERE
|
||||||
|
hosts = 127.0.0.1
|
||||||
|
dbname = mailserver
|
||||||
|
query = SELECT 1 FROM virtual_mailboxes WHERE email='%s'
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Just the same story as with virtual domains. The database returns `1` if there is a `virtual_mailboxes` record matching
|
||||||
|
that specific email address.
|
||||||
|
|
||||||
|
Actually the _answer_ is usually not `1` but the path to the mailbox on disk. But that applies only if Postfix would be
|
||||||
|
saving the email. In our setup the email will be passed on to Dovecot in a later step. Dovecot will then handle the
|
||||||
|
files on disk. Postfix just has to know is whether a mailbox exists. And that's why any _answer_ is sufficient here.
|
||||||
|
|
||||||
|
### Tell Postfix
|
||||||
|
|
||||||
|
You have created the config files for the three mappings. Now you just need to tell Postfix to use them:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
postconf virtual_mailbox_domains=mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
|
||||||
|
postconf virtual_mailbox_maps=mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
|
||||||
|
postconf virtual_alias_maps=mysql:/etc/postfix/mysql-virtual-alias-maps.cf
|
||||||
|
|
||||||
|
chown root:postfix /etc/postfix/*.cf
|
||||||
|
chmod o= /etc/postfix/*.cf
|
||||||
|
```
|
||||||
|
|
||||||
|
`postconf` is a command that changes configuration in `/etc/postfix/main.cf` and applies them instantly. You don't have
|
||||||
|
to restart Postfix.
|
||||||
|
|
||||||
|
The last two lines set the owner of the config files (_user=root, group=postfix_) and make sure that _others_ have no
|
||||||
|
access. After all a database password is found in these files.
|
||||||
|
|
||||||
|
### Test it
|
||||||
|
|
||||||
|
Give the mappings a quick test using the `postmap -q` command (_q_ stands for _query_):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
postmap -q example.org mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
|
||||||
|
postmap -q jack@example.org mysql:/etc/postfix/mysql-virtual-alias-maps.cf
|
||||||
|
postmap -q john@example.org mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
|
||||||
|
```
|
||||||
|
|
||||||
|
These three commands use the three mapping files (`*.cf`) to query your three database tables. The result should show:
|
||||||
|
|
||||||
|
```
|
||||||
|
1
|
||||||
|
john@example.org
|
||||||
|
1
|
||||||
|
```
|
||||||
|
|
||||||
|
If you get anything else, please check those three config files. Perhaps the database password is wrong?
|
||||||
|
|
@ -1,26 +1,15 @@
|
||||||
---
|
---
|
||||||
title: Receive emails
|
title: "Deliver emails: Dovecot"
|
||||||
lastUpdated: 2025-08-20
|
lastUpdated: 2025-08-22
|
||||||
slug: ispmail-trixie/receive-emails
|
slug: ispmail-trixie/deliver-emails-dovecot
|
||||||
sidebar:
|
sidebar:
|
||||||
order: 160
|
order: 165
|
||||||
---
|
---
|
||||||
|
|
||||||
import { Aside } from "@astrojs/starlight/components";
|
import { Aside } from "@astrojs/starlight/components";
|
||||||
|
|
||||||
import "@splidejs/splide/css";
|
In the previous chapter you have prepared Postfix to receive emails. As shown in the slideshow, Postfix hands incoming
|
||||||
import { Splide, SplideSlide } from "astro-splide";
|
emails over to Dovecot which in turn saves it to a mailbox on disk.
|
||||||
|
|
||||||
I have created a little slideshow to help you understand the process of receiving an email from a remote mail server.
|
|
||||||
Use the arrows to navigate between slides:
|
|
||||||
|
|
||||||
<Splide>
|
|
||||||
{Array.from({ length: 24 }, (_, i) => i + 1).map((i) => (
|
|
||||||
<SplideSlide>
|
|
||||||
<img src={`/big-picture-receive/${String(i).padStart(2, "0")}.svg`} alt={`Slide ${i}`} />
|
|
||||||
</SplideSlide>
|
|
||||||
))}
|
|
||||||
</Splide>
|
|
||||||
|
|
||||||
So:
|
So:
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue