Configurar correo electrónico entrante de entrega directa para sitios autohospedados con 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 Me gusta

7 publicaciones se dividieron en un nuevo tema: ¿Funciona el receptor de correo con arm?

Este contenedor parece codificar el correo electrónico en un parámetro llamado email:

Esto parece estar obsoleto, según /logs:

Aviso de deprecación: advertencia: el parámetro email está obsoleto. todas las solicitudes POST a esta ruta deben enviarse con un parámetro email_encoded codificado en base64 estricto en su lugar. Se ha recibido el correo electrónico y está en cola para su procesamiento (eliminación en Discourse 3.3.0)

¿Podrías actualizar esto antes de eliminar el parámetro obsoleto? :innocent:

6 Me gusta

Algunas notas de la configuración de esto:

  • Asegúrate de proteger el nuevo archivo de configuración del contenedor con: chmod o-rwx containers/mail-receiver.yml. Si no lo haces, se te pedirá que lo hagas al iniciar el contenedor.
  • Al crear la clave de API, seleccioné el ámbito “Todos los usuarios” y “Global”. No sé si una clave más restringida funcionaría.
  • El archivo de ejemplo mail-receiver.yml tiene configuraciones TLS bastante diferentes, por lo que querrás usar las instrucciones de aquí en lugar de intentar editar el ejemplo.
  • También incluye una configuración smtpd_tls_security_level que descomenté. No investigué para averiguar si es necesaria o si estaría mejor con una configuración diferente a “may”.
  • Si deseas configurar un correo electrónico para una categoría en particular, eso se puede hacer en /c/{category-name}/edit/settings. (Esto es útil si deseas crear una especie de categoría de lista de correo). Para un grupo, puedes configurar una dirección de correo electrónico en /g/{group-name}/manage/interaction.

No sé si algo de esto ayudará a otros, pero me habría ayudado a mí. :wink:

4 Me gusta

Realmente deberías usar una clave de API de mínimo privilegio, estas configuraciones definitivamente funcionan:

Nivel de usuario: Todos los usuarios
Ámbito: Granular
email—recibir correos electrónicos

4 Me gusta

Por lo que puedo decir, mi configuración es correcta, pero no hay registro de ninguno de los correos electrónicos en Discourse.

Los correos electrónicos aparecen en el registro de esta manera:

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

Y también recibo una notificación de rechazo en la dirección de envío. No queda nada en la cola y no hay errores de rastreo en los registros. Verifiqué tres veces que las URL coinciden y la página de configuración de la API muestra que se está utilizando la clave. Pero la lista de correos rechazados en el panel de administración permanece vacía.

¿Alguna sugerencia?

El error sugiere que MAIL_DOMAIN no está configurado como www.[mysite].com, o bien no hay ninguna categoría o grupo configurado para recibir correos electrónicos enviados a test004@www.[mysite].com.

1 me gusta

Gracias por tu respuesta. He revisado MAIL_DOMAIN de todas las formas que puedo imaginar, he probado todas las combinaciones de valores de MAIL_DOMAIN y direcciones de correo electrónico de destino. ¿Contra qué valor en la configuración de Discourse se comprueba, por ejemplo, DISCOURSE_HOSTNAME, DISCOURSE_SMTP_DOMAIN, u otro?

Me confunde un poco tu segunda sugerencia dada esta línea en el OP:

¿No deberían aparecer los rechazos incluso antes de que Discourse esté configurado para hacer algo con ellos? Los rebotes tampoco aparecen, he probado usando el método recomendado aquí: Configurar VERP para manejar correos electrónicos rebotados. No hay rastro de nada en admin/email.

¿Hay algún registro en alguno de los contenedores que muestre (o pueda configurarse para mostrar) más información sobre la interacción entre mail-receiver y app?

1 me gusta

Hay dos tipos principales de rechazos: los que ocurren temprano y deciden si el correo electrónico debe pasarse al EmailReceiver de Discourse o no, y los que ocurren durante el procesamiento de correo electrónico de Discourse.

En mi experiencia, los primeros no aparecen en los registros de Discourse, lo que significa que la mayoría (¿todos?) los rechazos relacionados con el correo electrónico (DMARC fallido, dirección incorrecta, etc.) no aparecen allí. Los que sí aparecen son cosas como que el correo electrónico es demasiado corto, el usuario no tiene permiso para publicar, etc.

No estoy seguro de si algo ha cambiado desde que se escribió ese párrafo, pero mi experiencia ha sido la anterior durante aproximadamente 2.5 años, desde que lo configuré.

Si envío un correo electrónico a test-reject@[mi-instancia], recibo un aviso de rebote genérico de mi proveedor de correo electrónico (no de mail-receiver / Discourse) que me dice que la dirección del destinatario fue rechazada. Esto se debe a que mail-receiver la está rechazando durante la interacción SMTP.

Los rebotes y VERP están relacionados con los correos electrónicos que tu instancia de Discourse está enviando, en lugar de recibir, por ejemplo, para detener automáticamente el envío de correos electrónicos de notificación a una dirección que rebota constantemente. No están relacionados con mail-receiver.


Sospecho que tu cita de la guía te ha confundido y, de hecho, probablemente todo esté funcionando correctamente. Enviar a alguna-direccion-aleatoria@MAIL_DOMAIN no será aceptado y no aparecerá en los rechazos, por lo que no es una prueba muy útil por sí sola (además de asegurarse de que mail-receiver está recibiendo correos electrónicos, lo cual has visto que sí hace).

Navega a una categoría existente o crea una nueva, abre su configuración y ve a la pestaña de configuración. Cerca de la parte inferior encontrarás dirección de correo electrónico entrante personalizada. Configúrala en algo@MAIL_DOMAIN, por ejemplo, la dirección test004 que intentaste anteriormente, guarda y luego intenta enviar a esa dirección.

Eso debería pasar por mail-receiver, por lo que deberías ver una nueva publicación creada en la categoría o un rechazo en Discourse.

1 me gusta

Gracias, esta es una aclaración muy útil, la configuraré y la probaré para ver.

En cuanto a los rebotes, me confundió de nuevo el OP, ya que los rebotes son el primer punto en la lista de razones por las que podrías querer seguir esta guía.

Entonces, incluso con esta configuración, e incluso si he eliminado mis registros MX de Mailgun, ¿todavía necesito configurar VERP en ese lado para capturar rebotes, etc.? Bueno, vaya, pensé que la entrega directa era una solución alternativa a mis problemas con los webhooks de Mailgun, parece que tendré que empezar a solucionar eso de nuevo.

2 Me gusta

Oh, disculpas, tienes razón en que dice que puedes usar mail-receiver para los rebotes, no estoy muy familiarizado con cómo funciona eso.

Mi mail-receiver no recibe rebotes, pero estoy usando webhooks de Mailgun, quizás Mailgun está cambiando el remitente del sobre para que reciba los rebotes si los webhooks están habilitados. (Es decir, si los webhooks estuvieran deshabilitados, quizás mi mail-receiver estaría recibiendo los rebotes en su lugar).

1 me gusta

Sí, estoy bastante seguro de que eso ya no es exacto, ya que el rechazo rápido se implementó en… (revisa git log) mayo de 2017.

Sin ver tu configuración real, incluida la configuración de grupos/categorías de Discourse, es muy difícil decir qué está saliendo mal. Sin embargo, el 80% de las veces es un error tipográfico en alguna parte; pídele a un colega (no tiene que ser alguien muy técnico) que lo revise, y probablemente detectará dónde pusiste una l en lugar de una i en unos cinco segundos. Mi esposa hace eso por mí habitualmente.

Lo es. Con la entrega directa, tu proveedor de correo saliente no necesita involucrarse en absoluto para el correo entrante. Todo, ya sea un nuevo tema, una respuesta o un rebote, debería ir directamente a mail-receiver (y de ahí a Discourse para su procesamiento).

3 Me gusta

Estoy bastante seguro de que eso es lo que me pasó con este mismo problema la semana pasada. Finalmente copié otro archivo YML de otro lugar y funcionó.

Sin embargo, me pareció extraño, Matt. Miré en los archivos de postfix y también parecían correctos, pero decía que el nombre de host no coincidía. Juro que lo copié y pegué, pero, tal vez cometí el error de pensar que podía escribir.

1 me gusta

Es bueno que el reconocimiento de voz de IA arregle todo eso para nosotros cualquier día. :troll:

3 Me gusta

[quote=“Simon Manning, post:476, topic:49487, username:Simon_Manning”]
Sospecho que tu cita de la guía te ha confundido y, de hecho, todo probablemente está funcionando correctamente.
[/quote]Tenías razón, configurar un correo electrónico para una categoría y enviar un correo electrónico allí funcionó como se esperaba, así que solo estaba golpeando mi cabeza contra la pared porque los rechazos eran silenciosos.

Me alegro de saberlo ahora, y espero que la guía se actualice, aunque personalmente preferiría que funcionara como describe la guía. Por ejemplo, si los usuarios intentan enviar un correo electrónico a alguna dirección y falla, podría ayudarme a informarles o darme cuenta de que hay demanda para comunicarse con una categoría o grupo por correo electrónico. Parece que sin eso, no hay una manera fácil de ver esos correos electrónicos.

[quote=“Matt Palmer, post:479, topic:49487, username:mpalmer”]
Lo está. Con la entrega directa, tu proveedor de correo saliente no necesita involucrarse en absoluto para el correo entrante. Todo, ya sea un nuevo tema, una respuesta o un rebote, debería ir directamente a mail-receiver (y de ahí a Discourse para su procesamiento).
[/quote]Esto todavía no está funcionando como se esperaba. Tengo los webhooks funcionando, así que puedo ver varios rebotes, pero sé que son de los webhooks de Mailgun porque tienen el problema descrito aquí: “Discourse::NotFound” error when click “Email Type” field on admin/email/bounced

Realmente no entiendo cómo Mailgun está recibiendo los rebotes en primer lugar, ya que no tengo ningún registro MX apuntando a sus servidores, ¿supongo que están configurando una ruta de retorno cuando envían el correo saliente?

Y veo los rebotes en los registros de mail-receiver, pero no llegan a app. Parece que están siendo rechazados silenciosamente. Aquí hay una línea en los registros que puedo conectar con un rebote recibido a través de los webhooks:

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>

¿Necesito agregar bounce+{%something}@www.mydomain.com como una dirección en la lista blanca en algún lugar para que pasen?

2 Me gusta

Sí, probablemente están reescribiendo la ruta de retorno (también conocida como “envelope from”) cuando el correo saliente pasa por sus servidores. Probablemente haya una configuración en algún lugar para desactivar eso, pero no he usado Mailgun, así que no puedo decirlo con seguridad (ni dónde estaría esa configuración).

OK, ese es un error entre mail-receiver y Discourse. Debería haber una línea en los registros poco antes de eso que comience con “Failed to GET smtp_should_reject answer” que te dirá más sobre qué falló y por qué, y eso debería correlacionarse con un mensaje de error de algún tipo en los registros de Discourse.

2 Me gusta

Mar 21 17:02:21 discourse-smtp-fast-rejection[1149]: Falló al obtener la respuesta de smtp_should_reject de https://www.mydomain.com/admin/email/smtp_should_reject.json: 400

¿Podría estar relacionado con el remitente nulo, from=\u003c\u003e? No veo nada en los registros al respecto. ¿El 400 indica que smtp_should_reject.json no existe?

2 Me gusta

Si ese recurso HTTP no existiera, sería un 404, no un 400. No creo que un remitente nulo deba ser un problema, porque así es como se entregarán todos los rebotes. Una clave de API incorrecta debería (creo) devolver un 403, pero no puedo decirlo con absoluta certeza de memoria, así que probablemente valga la pena verificarlo, por si acaso. Si los registros de Discourse no dan ninguna indicación de por qué la solicitud fue incorrecta, me temo que probablemente te espera una sesión de depuración dolorosa; no tengo un sistema habilitado para mail-receiver con el que pueda jugar fácilmente en este momento. Sería un trabajo de consultoría para mí llegar al fondo de lo que está sucediendo y solucionarlo por ti, me temo.

3 Me gusta

Por ahora, no parece estar rompiendo nada, tengo los rebotes funcionando con webhooks y la mayoría de los rebotes no generan un correo electrónico (otro tema menciona esta respuesta de stackoverflow, lo que coincide con lo que estoy viendo). Y la respuesta por correo electrónico también funciona como se esperaba. Sea cual sea el fallo, es raro y no está rompiendo la función normal.

Lo vigilaré por si acaso, y volveré a informar si descubro algo que pueda ser útil para otros. ¡Gracias de nuevo por tu ayuda!

2 Me gusta

@JammyDodger ¿Se podría cambiar el nombre de esto a algo que permita buscar “mail-receiver” para encontrarlo? Mayormente no he podido encontrar este tema sin varios intentos desde hace tres años, cuando se eliminó “straightforward” del título.

4 Me gusta