feat: add docker-mailserver for self-hosted outbound SMTP
Some checks failed
CI/CD / syntax-check (push) Successful in 1m6s
CI/CD / deploy (push) Failing after 18m22s

Adds docker-mailserver (SMTP_ONLY mode) to the tools stack so Outline
can send magic-link emails without depending on an external SMTP provider.

Changes:
- docker-compose.yml.j2: add mailserver service + mail-internal network
  outline gets mail-internal network to reach mailserver
- env.j2: point Outline SMTP at local mailserver:587 with noreply account
- defaults/main.yml: add mailserver_image (v14)
- tasks/main.yml: create mailserver dirs, wait for postfix ready,
  idempotent account creation, DKIM key generation + DNS instructions
- inventory/group_vars/all/main.yml: add mailserver_noreply_password alias
- vault.yml: add vault_mailserver_noreply_password

After deploy, Ansible will print DKIM/SPF/DMARC DNS records to add
to Cloudflare.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jack 2026-03-22 16:28:29 +07:00
parent bf59b75c8f
commit b616c18c58
6 changed files with 219 additions and 120 deletions

View file

@ -47,6 +47,7 @@ outline_utils_secret: "{{ vault_outline_utils_secret }}"
outline_db_password: "{{ vault_outline_db_password }}" outline_db_password: "{{ vault_outline_db_password }}"
n8n_encryption_key: "{{ vault_n8n_encryption_key }}" n8n_encryption_key: "{{ vault_n8n_encryption_key }}"
n8n_jwt_secret: "{{ vault_n8n_jwt_secret }}" n8n_jwt_secret: "{{ vault_n8n_jwt_secret }}"
mailserver_noreply_password: "{{ vault_mailserver_noreply_password }}"
# Server IPs (used for cross-server Traefik routing) # Server IPs (used for cross-server Traefik routing)
ip_main: "87.249.49.32" ip_main: "87.249.49.32"
ip_tools: "85.193.83.9" ip_tools: "85.193.83.9"

View file

@ -1,106 +1,110 @@
$ANSIBLE_VAULT;1.1;AES256 $ANSIBLE_VAULT;1.1;AES256
31636461643831333531333865363466633066313930383165643866653465326136373363643830 64646131626662663937643431386162343036353133623631303531353735653862353835303438
3133373732303438613337353435333764643761356534300a376331636161373664393634376265 6637363563353433326235383231633363326533653634660a316436323933373162316463343063
66373962303534383636663137306531346564303533613235613935613632373338343630306339 32656364646331363232376632353737303330356633336261383561333165623664666130636335
6133633166396138360a356463323462643266336132303365313363343062346265343961363431 3734366139333039360a353034636336313362383734623264616439303734323763343535616661
32373838326536396630643636356461663539313131376538656261376166363062343133666262 34336136663561336334666430376566353865363837613834326336383563373131666562663238
36386430353963323435343937613539326537333463326538613464356631616362396636366166 37636638613966373162633565366361613965636232393266373039313931323361303130356463
34373333363232326639303563343965373733646264383263666266393265313732303036613538 35653132336232393934626631366137346366373635636633346530633134336264663761316564
31356633376631303533613462643836323462333930306331666563326536303166336339356633 31626230363562343035323334373364316333306162353565356461373938653235303562383134
39393563346139666533376535633166643336646166663962623730346430663363653537623931 35623561313165366364613036643264303738613038316163636164663833333635356561646438
38663466616661393532326136636230633363363164626239616438353737353830653730613936 37336265363835326363313964333033636537366232313336633932386261373031663338343334
31373037656331356563333031393861633435333364653165633237323532353962383066636462 35336664313836336434666237663539386337656461396264613562313536653463393231306463
64646666323235323432336135333466613265363738643630346539636465383833366139303634 33633937356564643235383562346461373563656435626139643530326236313030396638323365
66373961663139396464393764353130666331623836386338343862323732346432643030626238 32393531363064343030333461343739633363616665376262663831303630653034633563313834
34336563356266333434353035363034363532656532303730363432633435383034393666393439 31373938313962386634383937393031633263383536613964373562393438643332656635306632
38626235646365366536303231633635353937666535666630323938303039323666653730383731 37613634666533646462626661646463326164323361306337316463653536323938373866633834
36313434616466353538626236333965323861376665383836343136376365303730336130313134 32336338333465306131396261383933313538323163636332323962316162306234323263336239
63613335633837313130626339626136646334333931666364376535356163363864656265336433 35663965626662633134363066323831623630326365376432616464326532636435313965613561
39333934333637323534343236366432333337373135333762333036343365383438326636613033 37343031373764326639646365663663633765393230323466343539303034323939336330383836
33373562373661616237323636343064623831363637313338343631343739363230623164656466 30343135313466396136326461333131336232656530656264383563363166323364636338653165
64323761383533643136393539613331353261396663316236613336663738636463633132653534 65663963373365646661616661393766653964663135643431646535333335353162623866326663
38653634393965383765656431343830386163333433323961353764316565326638303434656161 37356534316437653130316531323065636335613931366563626639393439623734653865326636
31383735356238643761323733353138313934386137633530636430643934363136373666653862 66356565303637613639626634313464333033373231656432343765363332666232656162386336
32383634396263326563656364313461636530366336336264326538643830396262326364633437 66666563356465363263373936366238643535633364663263383337396565313337353537623061
32656566363763396532316334333331353334316662636330396134613363663034303162323133 63346536663765623535363037323536633730306135636566663866663763613137653033666435
62363235303532663536383236326263646366333732333638363631326131306233623035306663 32653661333865613632323339633631643637663438333065336666666562663535633135316639
62333035623566636235663963316430303932313166313439396135373033653364376263633937 31333563663130396164386535373335613364643966623164353639393536343866666437613137
33616233663333316438333834313831383232666531636336666634316361386538323364366532 65636564343164306466333333333930316337623161363163656365653465313135393233363562
62343335333836646231386164333533366638626437333531303263656262356566363462363561 39396437363239643233653938343030386539666332666130303764373138633236326534636638
39326430366363623139393461366265613833393638626337636632376364326461313934653939 62336630666331646365643039386665343964346336343837336639653864653331323438323436
37336136633161393439383238666537643530616535333861323635386433356463646266326666 38376361613161376133323066633930333165343939376135376531363939303139326161333030
36343735316464306238376439663035356331366433326134633631386531313638663265363532 39666366613665346330303863666634393366396439656133653038623466383838343833626365
63336366646533333662323739316561393537383033346538363763303333643538363264656230 35346666316265393561303563313333636337323334316534303637303133303639613838346633
32653335643039613166386463326662393336363532313064633862313761623265333665356132 31336437633733646237646432653462323462363165393665363564636566666161333436363163
33646131336561393333363562653566663062353064383834663430613365323234306336343636 33323439636138343064613530633132616264626338336132356234313863393934646133313933
64383661323761326534326564313465323333623631643962313163316533343361323836346461 36626133613862616130666464386361393265333064373538353435376231346134343066633939
62623839343530366236323338626134613761396638303230336134303537306338623364376234 61346561633933623336653462623764383938616466643138346164303863303734626162383431
35653162376533623636616538336337646330373765366133326134653334313032343733336436 37623036613032353431646332623735393265656533373536313065393665663433376235663361
37636462333239326632643830643463623534306337333531646462376434343163353030643430 66373934646133623264363433643437316261343739316164363065653263373539383462623439
61343933373339376639316330363139653234636635303739633239613566613634666339643832 64663733336535613736653731623137613137653962363262303836306162616339626430353662
62616266346232353737376139626233363335363432323338353935343737663938656630653238 65653739336136623934333837663966663838393164353933333039306339323131613535636333
66313231353438346431333533366664313661663330623466316663366337373531636663643632 39383836373061353164323337643135383164383434333465643331346230326633643965366531
30353863383635353764633436613964646631373730313232346664343463653163323030643266 34316635636464616162666234616164393636616165326535336435373633323036313131653964
66646634373862633361656464646539313433613363313861376462653731383663386662643062 62666363313836633765393862396235633434353835373961393164333538313965623266333839
64333164623130666233366536363761343138633935363164653066373837666339323165333362 64303363373862613266383262643966613738376434343263346538623937346235616534373534
34616239373062623332383562653961343963386431643665306230656664613766333733353834 64316235386231373736663934636335343236316563626161323437323831653236383139313936
62396430616564303830326531643336363631363234626635636133313435626539646433613361 35666664616632643030643536323336316535646130633762333166353632613139363964386462
64653437363036363461626364393336323336323634393239626631353430646466343164623465 62323764333930653534613834313964323832336161333538626531383631363238316634336131
35623130346663376336336530366161623361663034373433313430636236616661316666346665 39616130626338343861613337383564636265383032313833366264333231393633666432373234
64303137323362313938363530316462363666396138663764306561393434636331653137343063 66646137303336643937313938326236643765636330613064336632343631623631613831336332
32306136313263373432336162363737306166663666646264666438363537346363613030653733 38333364396338613065643939346161336431313261353761616461393161643534623364393235
32653866306134623366616231393234653338653265636137343136356430633236623063306333 36306337396363336231343230323536623933346634363037333135353530643665663034633134
61373437393831323830393663663832653939616437373963383934336266373434323133336239 35393839343865373561623561353739396261343833373137616663386164326465323234393938
62626334323266343739643932663961616130386435636230613165366232343138346435353739 63306535623331366462383961373639366631353738313534656230376365323436353165346663
65393464326531323034333864646661346465633537373632613431663035643536623965336334 33663939353463373866366230366463633733623266613135343765393431663436666466363831
65363431343362613639353361313365643438376230353934646466643239316435396266326231 38323561663565363862633037303237613231333435646564383730623462653333326633316334
37633362326637323265343437356138633461303031323533613963396536323164666461353034 65643661363036613837303366323261633563313338323663303064623937653837623861376636
65373763626362313766623030316164303964633830613337613139613163396439373430633738 61323437656139363461386535396436386564316662396361396133663834353064376634326565
38663839646361663164343261656536356337613531393136636134383363333366633866623664 37393433656532653638303433376332643536666635383334666161393664616265613430313337
65373364363734666366393462323362326564333431393131303264646536663539363066373938 31373936646430383132613535343562643638363666323162386638643263613436643562333631
62643162346661643333313134623865666666643736313235346638353363346435656439353335 64363034323232303161396536396136373163663035393238346462616539366637356233386363
36326534656338383666316537303734343462303136653264393538303038373131616664363434 63343062373062666363393663396633643331626561643133303866313864353831323162623962
38376131333734303039396232343265303239333330623361666462363261366365626461613034 61356237363235653038663132623263333565333065363361363362303431623462343361326638
35616361373631366233623464666261626135353064323635363766356331303864396433323865 34316139313635366435393365643136303264626264643961643938633238346161616339653535
32366365623730353033373039626235306166323035393331363839356162323363326366613065 37656238623762363564626365316361303730303936626637323066343537656231326136666134
31303630363763663561326238663533633537376561303935363734306234343063393863353738 38326538363230393533383439666165353037636162626538326536616635393831313031613133
38346365373331383963643430633164313632323133323838643436646137313361653631376430 38333137393361383931336437363434303232633733623832343534343039643038373932656361
38323964383264653237343334386638366463393461653239303734656537616261383361376464 35316464663936306632326632323462376435633234386261383338663034336132336338353836
32666266363039316365303563346133356632653366303661626331313763306538386533336137 64646433383337333662623861303632653636333332396466613439643565326331376535333466
62373336366466306634323539623439336632333036313361346164316666396333623832393732 30333661366661303531623437393265353736646464653535336162383737333832383966306366
32393261393865353362383434326137633963613265343136663834666366633136646335653064 38363136663732383335303966636161653833313338393137373463303465326239666161303939
64306162656131636439316636353463383638323938306662336134323035656262386462616339 62363761646539383462313464353332356634333762383836366334386236363062323131316436
65373431616332613234353230653365313935646430363435616166323238643533313932356134 65313465373038653237633963316138353339366666343133316366396236343831313035653665
38376535373739616338303065623863303030613464633330316337313238623234643862356537 62383135383763346336393534396236303832336361616264373838666331363636323434363961
33313630663163626331623862373861613435393566633735636436643939323436616366353633 63363539313164393035333361373765626532383735643535316364646134656632353165646138
36623235386434613163303430366366633830376565383632613565393931326531323566353531 33386638366661396538366465666236656166613031653338396232336239393631353435373564
65653032383139666538373063383337306235393334383164626337623564663939653933363830 35333637313166353035626465653235396133303762623630333963313063613963363236663964
35663630636565636639653662633362643562356638643864383030316565636564306362336539 62623437326535643138346536333637643839343964306364313532383932373834653632383839
34376435353434393034613365613533346339353935353233663231623235623863623665616135 65303736353362303839623830653939316139376232373531313165346266306130633566393732
65313263643965343833373063616238313465373534613330613035346337343832386136343736 32636662343931383235646165626632636330663634643238653562373864346537333639656235
61333637393065653331353737663737376630383036646531353432653434363436323866663331 34653864393338383964303861393866333333666164376137386664346638356566386430353663
35656463663830343864643064313931633763373762613233663038653835643932616137646163 61363139353861633665636565393966663535333731396264636531303233663865366538656362
31356631346530653461343262303836306463393838666236613834623737666666643035653433 66333234613465663332663765386461376239396161383230363763616236303236636565616664
30366138663935613336303931396439343762663566366563303834306465323132363831306337 36306539323433303132333733653039666666636463646535613737653934383161656436636664
31363134383263663832383838356332326663663562646536313538653961623233636663336665 65346634633430666165316631636334333537393565623131636338623333313033336665346132
66373663356335333161336231313564633834353137643763616261333564656561346130623339 39313065653932643132333536353737326663616133326130623834396530663635616339363162
31366435303636646362653034633634636433353032353338373166316336653132373934313630 64316163376266653463333637373939376463656232666461306163376230353336633732653965
62656362303631636435303139383766383931626133336565633262323361326337666362353538 64386139356336353265343233643761313261666463363738353631366233636463303931396162
66343532353532313964343330323666633231313765336535376431653836343866633334336437 34336439346139363832323333646365356630613930366431653766653633343634363066363639
62343538623663373039343134326235613130313534613537386466353130653764653435663666 61656535333662363865633130663431386633363336663730363934306435383838343933633231
34363532613635316538363964643435653062643736396362626534363064386536653963383633 30303138303237666236336461353164396336346236626666383065386365636236306435343233
32326335613336363332313036353338303738363865346362316339356166333131626339303663 63396436656364396662326364633636616366363531336363306333333831383339346232323261
39613531653763613638343839313162636239383030643134373035616361313936386333656633 33616164356464636165376235353835613966333730663833636530646430383134333437333333
36646238316566386238333033636439373633393034663466653635616237616566383466613465 39336664643937303462356330323066343633306164373064666634303862326261666238653938
39383461663131633461313261613339613633303935636639303337313965306637306538643036 65346266303665636464663564653064663839663261666665336635613934333933326666306262
33643665616135643234393739333532356462303263363065613461623631643030666331316562 34333861626135386239656333323037663463326531663966313266616637636435383862396237
36313739356339623465353564363537666335313132663464346665383936346362643565326237 37316231666662343765326638313335353032636236633263646539646636636432343661326463
66336239343735336433636466313163353332333061646638616534656131313437383561653561 62393336646638646531313837393935356465613136373330363131663130323632626261646636
31613861386363326136303833343966313862353234373762356630623335613866666534376533 35656166306437643631663261656166313232373339666265393033656631366532346163333038
63303034333630623164373064383730356136373961646465663766303031303932343664396163 38306431323264666461646661646537363235386434326362346239356466663137363135383537
32646335303562313565386434353036646463343336646665333334336339303465613530373234 61613363646235376633303232316564373731666537373032633937393832393935313039656464
31663461643664313136396633613532363766363635363132323432643161373130333635646335 66623236666564313665343461343337326233343131623331636635363131396339383266633461
33376438323132656335633862653630356534396339303739303538383837343031386232346137 63323063363138353266643433616439333433353032313861363932336336656536343839626536
63366530313661313731326664363936373163383066656635616137653132613134636635346531 32316434376536316232666430653561313661656264383665386663356562303132393666333064
36353130333461323230626239636234326236643366383265626535356130363538653735626337 36306236363137383762626431373937313933653735326131383038636263396431393037373235
30386633373366383339346431656239383136313838363032333663366238623835 33376234366265343966356462306134663734363837323464313137653863643436393437653065
61643935633862303330653666376261326537386564396330636561656230633630393466666364
31303134636262616461393465616635663061306330316139623462306266346464303034323066
37333830323335303065626436343837636462616263663434303563333430386663393465303862
3261

View file

@ -4,10 +4,4 @@ outline_image: "outlinewiki/outline:0.80.2"
outline_db_image: "postgres:15-alpine" outline_db_image: "postgres:15-alpine"
outline_redis_image: "redis:7-alpine" outline_redis_image: "redis:7-alpine"
n8n_image: "n8nio/n8n:1.89.2" # https://hub.docker.com/r/n8nio/n8n/tags n8n_image: "n8nio/n8n:1.89.2" # https://hub.docker.com/r/n8nio/n8n/tags
mailserver_image: "ghcr.io/docker-mailserver/docker-mailserver:14" # https://github.com/docker-mailserver/docker-mailserver/releases
# SMTP for Outline magic-link auth (override in vault)
outline_smtp_host: "smtp.csrx.ru"
outline_smtp_port: 587
outline_smtp_from: "noreply@csrx.ru"
outline_smtp_username: ""
outline_smtp_password: ""

View file

@ -7,6 +7,19 @@
group: "{{ deploy_group }}" group: "{{ deploy_group }}"
mode: "0750" mode: "0750"
- name: Create mailserver directories
ansible.builtin.file:
path: "{{ tools_root }}/mailserver/{{ item }}"
state: directory
owner: root
group: root
mode: "0755"
loop:
- mail-data
- mail-state
- mail-logs
- config
- name: Deploy docker-compose.yml - name: Deploy docker-compose.yml
ansible.builtin.template: ansible.builtin.template:
src: docker-compose.yml.j2 src: docker-compose.yml.j2
@ -32,9 +45,61 @@
- "{{ outline_db_image }}" - "{{ outline_db_image }}"
- "{{ outline_redis_image }}" - "{{ outline_redis_image }}"
- "{{ n8n_image }}" - "{{ n8n_image }}"
- "{{ mailserver_image }}"
- name: Start tools stack - name: Start tools stack
community.docker.docker_compose_v2: community.docker.docker_compose_v2:
project_src: "{{ tools_root }}" project_src: "{{ tools_root }}"
state: present state: present
pull: missing pull: missing
- name: Wait for mailserver to be ready
ansible.builtin.command: docker exec mailserver postfix status
register: postfix_status
changed_when: false
retries: 12
delay: 10
until: postfix_status.rc == 0
- name: Check if noreply mail account exists
ansible.builtin.command: >
docker exec mailserver setup email list
register: mail_accounts
changed_when: false
- name: Create noreply mail account
ansible.builtin.command: >
docker exec mailserver setup email add noreply@{{ domain_base }} {{ mailserver_noreply_password }}
when: "'noreply@' + domain_base not in mail_accounts.stdout"
- name: Check if DKIM key exists
ansible.builtin.stat:
path: "{{ tools_root }}/mailserver/config/rspamd/dkim/{{ domain_base }}.private"
register: dkim_key
- name: Generate DKIM key
ansible.builtin.command: >
docker exec mailserver setup config dkim domain {{ domain_base }}
when: not dkim_key.stat.exists
register: dkim_generated
- name: Show DKIM DNS record
ansible.builtin.command: >
cat {{ tools_root }}/mailserver/config/rspamd/dkim/{{ domain_base }}.txt
when: dkim_generated is changed
register: dkim_record
- name: Print DKIM DNS instructions
ansible.builtin.debug:
msg: |
══════════════════════════════════════════════════════════════════
DKIM key generated! Add this TXT record to Cloudflare DNS:
{{ dkim_record.stdout }}
Also add SPF record:
Type: TXT Name: @ Value: v=spf1 ip4:{{ ip_tools }} ~all
And DMARC record:
Type: TXT Name: _dmarc Value: v=DMARC1; p=none; rua=mailto:admin@{{ domain_base }}
══════════════════════════════════════════════════════════════════
when: dkim_generated is changed

View file

@ -12,6 +12,9 @@ networks:
n8n-internal: n8n-internal:
driver: bridge driver: bridge
internal: true internal: true
mail-internal:
driver: bridge
internal: true
volumes: volumes:
outline_db_data: outline_db_data:
@ -28,6 +31,7 @@ services:
env_file: .env env_file: .env
networks: networks:
- outline-internal - outline-internal
- mail-internal # send mail via mailserver
- front # needed for host port binding - front # needed for host port binding
ports: ports:
# Exposed only to main Traefik (access controlled by UFW) # Exposed only to main Traefik (access controlled by UFW)
@ -126,3 +130,35 @@ services:
options: options:
max-size: "10m" max-size: "10m"
max-file: "3" max-file: "3"
# ── Mail server (outbound SMTP for Outline magic links) ──────────────────────
mailserver:
image: {{ mailserver_image }}
container_name: mailserver
hostname: mail
domainname: {{ domain_base }}
restart: unless-stopped
networks:
- mail-internal
environment:
- SMTP_ONLY=1 # no IMAP/POP3, sending only
- ENABLE_RSPAMD=0 # lightweight: skip spam filter
- ENABLE_CLAMAV=0 # no antivirus needed for outbound-only
- ENABLE_FAIL2BAN=0 # host fail2ban already handles this
- POSTFIX_INET_PROTOCOLS=ipv4
- LOG_LEVEL=warn
- OVERRIDE_HOSTNAME=mail.{{ domain_base }}
- POSTMASTER_ADDRESS=admin@{{ domain_base }}
volumes:
- {{ tools_root }}/mailserver/mail-data:/var/mail
- {{ tools_root }}/mailserver/mail-state:/var/mail-state
- {{ tools_root }}/mailserver/mail-logs:/var/log/mail
- {{ tools_root }}/mailserver/config:/tmp/docker-mailserver
stop_grace_period: 1m
cap_add:
- NET_ADMIN
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"

View file

@ -27,14 +27,13 @@ FILE_STORAGE=s3
# Auth — local accounts (can add OIDC/Authelia later) # Auth — local accounts (can add OIDC/Authelia later)
AUTH_PROVIDERS=email AUTH_PROVIDERS=email
# SMTP (required to enable email magic-link auth) # SMTP — local docker-mailserver container (same Docker network, port 587 with auth)
SMTP_HOST={{ outline_smtp_host }} SMTP_HOST=mailserver
SMTP_PORT={{ outline_smtp_port | default(587) }} SMTP_PORT=587
SMTP_FROM_EMAIL={{ outline_smtp_from }} SMTP_USERNAME=noreply@{{ domain_base }}
{% if outline_smtp_username is defined and outline_smtp_username %} SMTP_PASSWORD={{ mailserver_noreply_password }}
SMTP_USERNAME={{ outline_smtp_username }} SMTP_FROM_EMAIL=noreply@{{ domain_base }}
SMTP_PASSWORD={{ outline_smtp_password }} SMTP_SECURE=false
{% endif %}
# Outline DB password (used in docker-compose) # Outline DB password (used in docker-compose)
OUTLINE_DB_PASSWORD={{ outline_db_password }} OUTLINE_DB_PASSWORD={{ outline_db_password }}