为自托管站点使用 Mail-Receiver 配置直接交付的收件邮箱

Discourse is all about enabling civilized discussion. While plenty of people like a web interface, e-mail is still the “hub” of many people’s online lives. That’s why sending e-mail is so important, and when you’re sending e-mail, you really want to be able to receive it, too. There are several reasons why:

  • If e-mails “bounce” (they can’t be delivered for some reason), you need to know about that. Repeatedly sending e-mails that bounce will get your e-mails flagged as spam. Receiving e-mail bounces allows you to disable sending to non-existent addresses.
  • Allowing people to reply to posts via e-mail can significantly improve engagement, as people can reply straight away from their mail client, even if they’re not able to visit the forum at that moment.
  • Letting people post new topics, or send PMs, via e-mail has similar benefits to engagement. In addition, you can use Discourse to handle e-mail for a group, such as an e-mail-based support channel (which is how Discourse’ own e-mail support is handled).

Delivering e-mail directly into your Discourse forum, rather than setting up POP3 polling, has a number of benefits:

  • No need to deal with gmail or another provider’s foibles;
  • You have more control over the e-mail addresses that people use to send posts; and
  • There are no delays in delivery – no more waiting for the next polling run to see new posts appear!

This howto is all about getting that hawtness into your forum.

Overview

This procedure creates a new container on your Discourse server, alongside the typical app container, which receives e-mail and forwards it into Discourse for processing. It supports all e-mail processes: handling bounces, replies, and new topic creation. Any self-hosted Discourse forum using our supported installation process can make use of this procedure to get easy, smooth-flowing incoming e-mail.

Container Setup

We’re going to get the mail-receiver container up and running on the server that’s already running your Discourse instance. There’s no need for a separate droplet just to handle mail – the whole container only takes about 5MB of memory!

So, start off by logging into your Discourse server, and becoming root via sudo:

ssh ubuntu@192.0.2.42
sudo -i

Now, go to your /var/discourse directory and create a new mail-receiver.yml container definition from the sample conveniently provided:

cd /var/discourse
git pull
cp samples/mail-receiver.yml containers/

Since every site is unique, open containers/mail-receiver.yml in your preferred text editor and change the MAIL_DOMAIN, DISCOURSE_MAIL_ENDPOINT, and DISCOURSE_API_KEY variables to suit your site. (If you are an advanced user and know that you are using nginx outside your container, see below for additional configuration for external nginx.)

:bulb: If you use the default mail endpoint (/admin/email/handle_mail), we suggest using the receive_email API key scope to provide an extra layer of security.

If you’re not sure what your favourite text editor is, try nano:

nano containers/mail-receiver.yml

Use Ctrl-X to exit (say “Yes” to “Do you want to save changes?”, or all your work will be for nothing).

Now, do an initial build of the container, and fire it up!

./launcher bootstrap mail-receiver
./launcher start mail-receiver

To check everything’s OK, take a peek in the logs:

./launcher logs mail-receiver

The last line printed should look rather a lot like this:

<22>Aug 31 04:14:31 postfix/master[1]: daemon started -- version 3.1.1, configuration /etc/postfix

If so, all is well, and you can go on to then next step.

DNS Setup

In order for everyone else on the Internet to know where to deliver mail, you must create an MX record for your forum. The exact details of how to do this vary by DNS provider, but in general, the procedure should be very similar to how you setup the DNS records for your forum in the first place, except that instead of creating an A (or “Address”) record, you’re creating an MX (or “Mail eXchange”) record. If your forum is at forum.example.com, and you set MAIL_DOMAIN to forum.example.com in the mail-receiver.yml, then the DNS record should look like this:

  • DNS Name: forum.example.com (this is the MAIL_DOMAIN)
  • Type: MX
  • Priority: 10
  • Value: forum.example.com (this is the domain of your forum)

To make sure the DNS is setup correctly, use a testing site such as http://mxtoolbox.com/ to look up the MAIL_DOMAIN you configured, and make sure it’s pointing to where you expect.

Note: outbound email providers like mailgun may ask you to add MX records pointing to their servers. You want to remove these so the MX records for your forum only point to your forum’s domain name. SPF and DKIM records must still point to your outbound email provider servers so you can send email.

Discourse Configuration

Now e-mail is being fed into Discourse, it’s time to explain to Discourse what to do with the e-mail it receives.

  • Log into your Discourse forum as Admin and navigate to the Admin panel’s Site Settings, then click the Email tab. (forum.example.com/admin/site_settings/category/email)
  • Change the following settings:
    • Enable the reply by email setting
    • In the reply_by_email_address field, enter replies+%{reply_key}@forum.example.com
    • Enable the manual polling setting

You can automatically, without any further setup, use any address @forum.example.com as an address for category or group e-mails.

Troubleshooting

Nothing ever goes according to plan. Here’s how to figure out what went wrong.

  1. OCI runtime create failed error running ./launcher start mail-receiver? Your hostname might be too long. Rename it using these instructions and choose a shorter name, then rebuild.
  2. Did the e-mail even make it to mail-receiver? Run ./launcher logs mail-receiver, and look for log entries that mentions the address that the e-mail was sent from and to. If there’s none of those, then the message never even made it, and the problem is upstream. Check MX records, sending mail server logs, and firewall permissions (SMTP port 25).
  3. Is the message stuck in the queue? Run ./launcher enter mail-receiver, then run mailq. It should report, “Mail queue is empty”. If there’s any messages in there, you’ll get the to/from addresses listed. Messages only sit around in the queue if there’s a problem delivering to Discourse itself, so exit out of the container and then check…
  4. Did mail-receiver error out somehow? Run ./launcher logs mail-receiver | grep receive-mail and look for anything that looks like a stack trace, or basically anything other than “Recipient: <something>@forum.example.com”. Those error messages, whilst not necessarily self-explanatory, should go an awfully long way to explaining what went wrong. Look for typos in your yml file. In particular, check that DISCOURSE_MAIL_ENDPOINT URL matches your site URL, usually starting with https://.

Integrating with External nginx

If you are an advanced user and have configured external nginx such as for Add an offline page to display when Discourse is rebuilding or starting up you will find that the combination of mail-receiver and HTTPS being handled in external nginx requires slightly different handling to enable SSL for email over TLS. Here are example containers/mail-receiver.yml snippets that work with the recommended configuration for external nginx with letsencrypt certificates:

  POSTCONF_smtpd_tls_key_file:  /letsencrypt/live/=DOMAIN=/privkey.pem
  POSTCONF_smtpd_tls_cert_file:  /letsencrypt/live/=DOMAIN=/fullchain.pem

volumes:
  - volume:
      host: /var/discourse/shared/mail-receiver/postfix-spool
      guest: /var/spool/postfix
# uncomment to support TLS
  - volume:
      host: /etc/letsencrypt/
      guest: /letsencrypt

Note that you can’t export as a volume only /etc/letsencrypt/live because the actual files are symlinks into ../../archive/... and those won’t resolve if you are more specific in the volume specification.

Prevent outgoing host email from interfering (Postfix)

If you have (or want) automated messages from your host server (via Postfix), the mail-receiver will conflict because it needs port 25 to operate. One solution is to disable the host Postfix from listening on port 25:

nano /etc/postfix/master.cf

and comment out the line that looks like this:

smtp inet n - y - - smtpd

Then service postfix reload. You may also need to restart the mail-receiver container.

With both the host Postfix and the mail-receiver running, do netstat -tulpn | grep :25 to confirm that docker-proxy is using port 25.

Block unwanted domains from sending to you

To stop email from unwanted domains from even reaching your Discourse, your mail-receiver.yml should look something like this:

  DISCOURSE_API_USERNAME: system

  POSTCONF_smtpd_sender_restrictions: 'texthash:/etc/postfix/shared/sender_access'

volumes:
  - volume:
      host: /var/discourse/shared/mail-receiver/postfix-spool
      guest: /var/spool/postfix
  - volume:
      host: /var/discourse/shared/mail-receiver/etc
      guest: /etc/postfix/shared
# uncomment to support TLS
#  - volume:
#      host: /var/discourse/shared/standalone/letsencrypt
#      guest: /letsencrypt

Then create /var/discourse/shared/mail-receiver/etc path, and within it create a sender_access file containing the domains to reject, like this:

qq.com REJECT
163.com REJECT

Rebuild and you’re golden!

DMARC support

DMARC support has been enabled by default in the discourse/mail-receiver:release image to more strongly validate incoming email. This is enabled since the timestamped image discourse/mail-receiver:20240720054629.

This functionality can be toggled via the INCLUDE_DMARC docker environment variable. If a more permissive incoming mail server configuration is preferred, set that environment variable to false and rebuild the image.

The last version without DMARC support is discourse/mail-receiver:20211208001915.

Further Reading

Last edited by @kelv 2024-07-22T03:53:51Z

Check documentPerform check on document:
95 个赞

7 帖已拆分到新主题:mail-receiver 是否支持 arm?

该容器似乎将电子邮件编码到名为 email 的参数中:

根据 /logs 的信息,这似乎已弃用:

弃用通知:警告:email 参数已弃用。此路由的所有 POST 请求都应使用 base64 严格编码的 email_encoded 参数发送。email 已收到并已排队等待处理(将在 Discourse 3.3.0 中移除)。

您能否在移除已弃用的参数之前更新它? :innocent:

6 个赞

设置此项时有几点注意事项:

  • 确保使用以下命令保护新的容器配置文件:chmod o-rwx containers/mail-receiver.yml。如果不这样做,在引导容器时会提示您执行此操作。
  • 创建 API 密钥时,我选择了“所有用户”和“全局”范围。我不知道更受限制的密钥是否有效。
  • 示例 mail-receiver.yml 文件具有非常不同的 TLS 设置,因此您需要使用此处提供的说明,而不是尝试编辑示例。
  • 它还包括一个 smtpd_tls_security_level 设置,我取消了注释。我没有做任何研究来弄清楚它是否是必需的,或者我是否应该使用比“may”更好的设置。
  • 如果您想为特定类别设置电子邮件,可以在 /c/{category-name}/edit/settings 中完成。(如果您想创建一个类似邮件列表的类别,这很有用。)对于组,您可以在 /g/{group-name}/manage/interaction 中设置电子邮件地址。

我不知道这些是否有助于他人,但它们本可以帮助我。 :wink:

4 个赞

您确实应该使用最小权限 API 密钥,这些设置肯定有效:

用户级别:所有用户
范围:精细
email—接收电子邮件

4 个赞

据我所知,我的设置是正确的,但 Discourse 中没有任何电子邮件记录。

电子邮件显示在日志中,如下所示:

Mar 18 17:20:41 [myserver]-mail-receiver postfix/smtpd[122]: NOQUEUE: reject: RCPT from [XXX].google.com[XXX.XX.XXX.XXX]: 554 5.7.1 <test004@www.[mysite].com>: Recipient address rejected: Mail to this address is not accepted. Check the address and try to send again?; from=<[sender]@gmail.com> to=<test004@www.[mysite].com> proto=ESMTP helo=<[XXX].google.com>
Mar 18 17:20:42 [myserver]-mail-receiver postfix/smtpd[122]: disconnect from [XXX].google.com[XXX.XX.XXX.XXX] ehlo=2 starttls=1 mail=1 rcpt=0/1 bdat=0/1 quit=1 commands=5/7

并且我也会在发送地址收到拒收通知。队列中没有任何残留,日志中也没有任何跟踪错误。我三番五次地检查了 URL 是否全部匹配,并且 API 设置页面显示正在使用该密钥。但是管理员面板中的拒收邮件列表仍然是空的。

有什么建议吗?

错误表明 MAIL_DOMAIN 未设置为 www.[mysite].com,或者没有配置任何类别或组来接收发送到 test004@www.[mysite].com 的电子邮件。

1 个赞

感谢您的回复。我已经尽我所能检查了 MAIL_DOMAIN,尝试了 MAIL_DOMAIN 值和目标电子邮件地址的各种组合。在 Discourse 设置中,它会与什么值进行检查,例如 DISCOURSE_HOSTNAME、DISCOURSE_SMTP_DOMAIN,还是其他什么?

鉴于 OP 中的这行文字,我对您的第二个建议感到有些困惑:

即使在 Discourse 设置为处理它们之前,不应该会显示拒绝的邮件吗?反弹邮件也没有显示,我已经按照这里的建议进行了测试:配置 VERP 来处理反弹电子邮件。在 admin/email 上没有任何踪迹。

是否有任何容器中的日志可以显示(或可以配置为显示)mail-receiverapp 之间交互的更多信息?

1 个赞

有两种主要的拒绝类型:一种是早期发生的,决定电子邮件是否应传递给 Discourse 的 EmailReceiver;另一种是在 Discourse 的电子邮件处理过程中发生的。

根据我的经验,前者不会出现在 Discourse 日志中,这意味着大多数(或所有?)与电子邮件相关的拒绝(DMARC 失败、地址错误等)都不会出现在其中。出现的情况是电子邮件太短、用户不允许发帖等。

我不确定自该段落写成以来是否有所改变,但以上是我大约 2.5 年前设置以来我的经验。

如果我发送一封电子邮件到 test-reject@[my-instance],我会收到我的电子邮件提供商(而不是 mail-receiver / Discourse)发来的通用退信通知,告知我收件人地址已被拒绝。这是因为 mail-receiver 在 SMTP 交互过程中拒绝了它。

退信和 VERP 与您的 Discourse 实例发送的电子邮件有关,而不是接收的电子邮件,例如自动停止向持续退信的地址发送通知电子邮件。它们与 mail-receiver 无关。


我怀疑您从指南中引用的内容让您感到困惑,实际上一切可能都正常工作。发送到 some-random-address@MAIL_DOMAIN 将不会被接受,也不会出现在拒绝列表中,因此这本身并不是一个非常有用的测试(除了确保 mail-receiver 正在接收电子邮件,您已经看到它正在接收)。

导航到一个现有类别或创建一个新类别,打开其配置并转到设置选项卡。在底部附近,您会找到 自定义入站电子邮件地址。将其设置为 something@MAIL_DOMAIN,例如您之前尝试过的 test004 地址,保存然后尝试发送到该地址。

这应该能通过 mail-receiver,然后您应该会在 Discourse 中看到一个新帖子被创建或一个拒绝。

1 个赞

谢谢,这非常有帮助的澄清,我会进行设置并进行测试。\n\n关于退信,我再次被 OP 搞糊涂了,因为退信是您可能想遵循本指南的原因列表中的第一点。\n\n所以即使设置好了,即使我删除了 Mailgun MX 记录,我仍然需要在那边配置 VERP 来捕获退信等?好吧,我以为直接投递是我与 Mailgun 网络钩子问题的解决方法,看来我需要再次开始排查这个问题了。

2 个赞

哦,抱歉,你说得对,它确实说你可以使用 mail-receiver 来处理退信,我对它的工作原理不太熟悉。

我的 mail-receiver 没有收到退信,但我正在使用 Mailgun 的 webhook,也许 Mailgun 正在更改 envelope sender,这样它就能收到退信(如果启用了 webhook)。(也就是说,如果禁用了 webhook,也许我的 mail-receiver 会收到退信。)

1 个赞

是的,我相当确定这现在不准确了,因为快速拒绝是在……(查看 git log)2017 年 5 月实现的。

如果看不到你的实际配置,包括 Discourse 群组/类别配置,很难说哪里出了问题。不过,至少 80% 的时间是某个地方出现了拼写错误;让同事(不一定是技术人员)看一下,他们可能只需要五秒钟就能发现你把 l 当成了 i。我妻子经常帮我检查。

是的。通过直接投递,你的出站邮件提供商在处理入站邮件时完全不需要介入。所有内容,无论是新主题、回复还是退信,都应该直接发送到 mail-receiver(然后由 Discourse 进行处理)。

3 个赞

我敢肯定上周我遇到这个完全相同的问题时也是这样。我最后从别处复制了一个 YML 文件过来,然后就好了。

不过,Matt,这确实很奇怪。我检查了 postfix 文件,它们看起来也没错,但它却说主机名不匹配。我发誓我进行了复制粘贴,但也许我犯了认为自己可以打字的错误。

1 个赞

很高兴人工智能语音识别很快就会为我们解决所有这些问题。 :troll:

3 个赞

[quote=“Simon Manning, post:476, topic:49487, username:Simon_Manning”]
我怀疑你从指南中引用的内容让你感到困惑,实际上一切可能都正常工作。
[/quote]你猜对了,为某个类别设置电子邮件并向其发送电子邮件确实按预期工作,所以我只是在徒劳地挣扎,因为退信是静默的。

我很高兴现在知道了,也希望指南能得到更新,尽管我个人更希望它能像指南描述的那样工作。例如,如果用户尝试向某个地址发送电子邮件但失败了,这可能有助于我让他们知道,或者意识到通过电子邮件与某个类别或组进行通信有需求。似乎没有它,就没有简单的方法可以看到这些电子邮件。

[quote=“Matt Palmer, post:479, topic:49487, username:mpalmer”]
是的。通过直接投递,你的出站邮件提供商在接收邮件时完全不需要介入。所有内容,无论是新主题、回复还是退信,都应该直接发送到 mail-receiver(然后由 Discourse 进行处理)。
[/quote]这仍然没有按预期工作。我已经让 Webhook 工作了,所以我可以看到一些退信,但我知道它们来自 Mailgun Webhook,因为它们遇到了此处描述的问题:“Discourse::NotFound”错误,当点击管理/电子邮件/退信上的“电子邮件类型”字段时

我不太明白 Mailgun 最初是如何收到退信的,因为我没有任何指向其服务器的 MX 记录,我猜想它们在发送出站电子邮件时设置了退信路径?

而且我在 mail-receiver 日志中看到了退信,但它们没有进入 app。看起来它们被静默拒绝了。这是日志中的一行,我可以将其与通过 Webhook 收到的退信关联起来:

NOQUEUE: reject: RCPT from mail-[id1].outbound.protection.outlook.com[XX.XX.XX.XX]: 450 4.7.1 <bounce+[id2]-[email]=[address].com@www.[mydomain].com>: Recipient address rejected: Internal error, API request failed; from=<> to=<bounce+[id#]-[email]=[address].com@www.[mydomain].com> proto=ESMTP helo=<[id3].outbound.protection.outlook.com>

我是否需要在某个地方将 bounce+{%something}@www.mydomain.com 添加为白名单地址,以便它们能够通过?

2 个赞

是的,他们可能在出站邮件通过其服务器时重写了退信路径(也称为“信封发件人”)。可能有一个设置可以关闭它,但我没用过 mailgun,所以不能确定(也不知道该设置在哪里)。

好的,那个是 mail-receiver 和 Discourse 之间的错误。日志中应该在出现该错误之前不久有一行以“Failed to GET smtp_should_reject answer”开头,它会告诉你更多关于什么失败了以及为什么失败的信息,并且应该与 Discourse 日志中的某种错误消息相关联。

2 个赞

Mar 21 17:02:21 discourse-smtp-fast-rejection[1149]: 从 https://www.mydomain.com/admin/email/smtp_should_reject.json 获取 smtp_should_reject 的答案失败:400

这是否与空发件人 from=<> 有关? 我在日志中没有看到任何相关信息。 400 是不是表示 smtp_should_reject.json 不存在?

2 个赞

如果该 HTTP 资源不存在,那应该是 404 而不是 400。我不认为发件人为空会是个问题,因为所有的退信都会以这种方式发送。不正确的 API 密钥(我认为)应该返回 403,但我现在不能绝对肯定地说,所以这可能至少值得检查一下,以防万一。如果 Discourse 日志没有显示请求不正确的原因,恐怕你只能进行痛苦的调试了——我现在没有一个启用了 mail-receiver 的系统可以轻松地进行测试。恐怕要解决这个问题并为你修复它,对我来说将是一项咨询工作。

3 个赞

目前,它似乎没有破坏任何东西,我已经通过 Webhook 启用了退信功能,并且大多数退信不会生成电子邮件(另一个话题提到了 这个 Stack Overflow 答案,这与我所见一致)。并且按预期工作的回复电子邮件。无论是什么故障,它都很罕见,并且不会破坏正常功能。

我会密切关注,以防万一,如果我发现任何对其他人有用的信息,我会及时汇报。再次感谢您的帮助!

2 个赞

@JammyDodger 能否将此重命名为允许搜索“mail-receiver”以找到它的名称?自从三年前从标题中删除“straightforward”以来,我大部分时间都无法在不重试几次的情况下找到这个主题。

4 个赞