Configure direct-delivery incoming email for self-hosted sites with 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件の投稿が新しいトピックに分割されました:メール受信者はARMで動作しますか?

このコンテナは、email という名前のパラメータにメールをエンコードしているようです。

/logs によると、これは非推奨のようです。

Deprecation notice: warning: the email parameter is deprecated. all POST requests to this route should be sent with a base64 strict encoded email_encoded parameter instead. email has been received and is queued for processing (removal in 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 がすべて一致していることを 3 回確認し、API 設定ページではキーが使用されていることが示されています。しかし、管理パネルの拒否されたメールのリストは空のままです。

何か提案はありますか?

エラーは、MAIL_DOMAINwww.[mysite].com に設定されていないか、test004@www.[mysite].com に送信されたメールを受信するように構成されたカテゴリまたはグループがないことを示唆しています。

「いいね!」 1

返信ありがとうございます。考えられるあらゆる方法でMAIL_DOMAINを確認しました。MAIL_DOMAINの値と宛先メールアドレスのあらゆる組み合わせを試しました。Discourseの設定で、どの値と比較されているのでしょうか?例えば、DISCOURSE_HOSTNAME、DISCOURSE_SMTP_DOMAIN、それとも他の何かでしょうか?

OPのこの行を考えると、2番目の提案について少し混乱しています。

Discourseがそれらを処理するように設定される前から、拒否されたものは表示されるはずではないでしょうか?バウンスも表示されていません。ここでは、Configure VERP to handle bouncing e-mailsで推奨されている方法を使用してテストしました。「管理/メール」のどこにも何も記録されていません。

mail-receiverappのやり取りに関する追加情報が表示される、または表示されるように設定できるコンテナのログはありますか?

「いいね!」 1

リジェクトには、メールが Discourse の EmailReceiver に渡されるかどうかを決定する早期のリジェクトと、Discourse のメール処理中に行われるリジェクトの 2 つの主要なタイプがあります。

私の経験では、前者 (早期リジェクト) は 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

ありがとうございます。非常に分かりやすい説明で助かります。設定してテストしてみます。

バウンスについてですが、このガイドに従うべき理由の最初の項目にバウンスが挙げられているため、OP(元の投稿者)の意図が再び分からなくなりました。

この設定を行っても、MailgunのMXレコードを削除した場合でも、バウンスなどを捕捉するために、そちら側でVERPを設定する必要があるということでしょうか? うーん、ダイレクトデリバリーがMailgunのウェブフックに関する問題を回避する方法だと思っていました。またトラブルシューティングをやり直す必要がありそうです。

「いいね!」 2

ああ、申し訳ありません、バウンスにmail-receiverを使用できると記載されているのは正しいですが、それがどのように機能するかはあまりよくわかりません。

私のmail-receiverはバウンスを受信しませんが、MailgunのWebフックを使用しています。おそらくMailgunはエンベロープ送信者を変更しているため、Webフックが有効になっている場合はバウンスを受信します。(つまり、Webフックが無効になっていた場合、私のmail-receiverが代わりにバウンスを受信していた可能性があります。)

「いいね!」 1

ええ、それはもはや正確ではないと私はかなり確信しています。高速拒否が実装されたのは…(git logを確認)2017年5月です。

実際の構成、特にDiscourseグループ/カテゴリ構成を見ずに、何がうまくいかないのかを正確に言うのは非常に困難です。少なくとも80%の時間はどこかにタイプミスがあるということです。同僚(それほど技術に詳しくなくてもよい)にそれを見てもらうと、おそらく5秒以内にiの代わりにlを入力した場所を見つけるでしょう。私の妻はそれを定期的に私にやってくれます。

そうです。ダイレクトデリバリーでは、受信メールのために送信メールプロバイダーがまったく関与する必要がありません。新しいトピック、返信、またはバウンスのいずれであっても、すべてが直接mail-receiver(そこからDiscourseで処理)に送信されるはずです。

「いいね!」 3

先週、この問題で私にも同じことが起こったと確信しています。どこか別の場所から別のYMLファイルをコピーしてきて、うまくいきました。

しかし、奇妙なことでした、マット。Postfixファイルを確認しましたが、それらも正しく見えましたが、ホスト名が一致しないと言われました。コピー&ペーストしたと誓いますが、タイプできると自分で思ったのが間違いだったのかもしれません。

「いいね!」 1

AI音声認識がすぐにすべてを修正してくれるので、良いことですね。 :troll:

「いいね!」 3

あなたの言う通りでした。カテゴリのメールを設定してそこに送信したところ、期待どおりに機能しました。そのため、拒否がサイレントだったために、ただ壁に頭を打ち付けていただけでした。

これでわかったので、ガイドが更新されることを願っていますが、個人的にはガイドの説明どおりに機能する方が良いです。たとえば、ユーザーが一部のアドレスにメールを送信しようとして失敗した場合、それを知らせるのに役立つか、メールでカテゴリやグループと通信する需要があることに気づくかもしれません。それなしでは、それらのメールを見る簡単な方法はないようです。

これはまだ期待どおりに機能していません。Webフックは機能したので、いくつかのバウンスを見ることができますが、それらはMailgunのWebフックからのものであることがわかっています。なぜなら、それらはここで説明されている問題を持っているからです:“Discourse::NotFound” error when click “Email Type” field on admin/email/bounced

Mailgunがそもそもバウンスをどのように取得しているのかよくわかりません。MXレコードは彼らのサーバーを指していませんが、送信メールを送信する際にリターンパスを設定していると仮定しますか?

そして、mail-receiver のログでバウンスを確認していますが、app には到達していません。サイレントに拒否されているようです。ログの次の行は、Webフック経由で受信したバウンスに接続できます。

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を使用したことがないので、確実なことは言えません(そのような設定がどこにあるかも分かりません)。

了解しました、それはメール受信者と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=\u003c\u003e に関連していますか?ログには何も見当たりません。400 は smtp_should_reject.json が存在しないことを意味していますか?

「いいね!」 2

もしそのHTTPリソースが存在しなかったら、それは400ではなく404になるでしょう。nullの送信者は問題にならないと思います。なぜなら、すべてのバウンスはそのように配信されるからです。APIキーが間違っている場合は、403を返すはずですが(そう思います)、すぐに断言することはできないので、念のため確認する価値はあるでしょう。Discourseのログにリクエストが不正だった理由を示す兆候がない場合、残念ながら苦痛を伴うデバッグセッションになるでしょう。現時点では、簡単に試せるmail-receiverが有効なシステムを持っていません。何が起こっているのかを突き止め、修正するには、コンサルティングが必要になるでしょう。

「いいね!」 3

今のところ、何も問題は発生していないようです。ウェブフックでバウンスが機能するようになり、ほとんどのバウンスではメールが生成されません(別のトピックでこのStack Overflowの回答が言及されており、私が目にしていることと一致しています)。また、返信によるメールも期待どおりに機能しています。失敗の原因が何であれ、まれであり、通常の機能には支障をきたしていません。

念のため監視を続け、他の人に役立つ情報が見つかった場合は報告します。ご協力ありがとうございました!

「いいね!」 2

@JammyDodger これを「mail-receiver」で検索して見つけられるように名前を変更できませんか? 3年前に「straightforward」がタイトルから削除されて以来、何度か試行錯誤しないとこのトピックを見つけることがほとんどできませんでした。

「いいね!」 4