Enhance e-mail invitations

- Built-in e-mail template
- More template placeholders
This commit is contained in:
Maxime Dor
2017-09-12 02:24:58 +02:00
parent cb0ffe0575
commit 55b759a31c
6 changed files with 168 additions and 25 deletions

View File

@@ -234,9 +234,25 @@ invite:
# The display name used in the e-mail # The display name used in the e-mail
name: "Matrix Identity" name: "Matrix Identity"
# The MIME content to send, UTF-8 expected # The E-mail template to use.
#
# The template is expected to be a full e-mail body, including client headers, using MIME and UTF-8 encoding.
# The following headers will be set by mxisd directly and should not be present in the template:
# - From
# - To
# - Date
# - Message-Id
# - X-Mailer
# #
# The following placeholders are possible: # The following placeholders are possible:
# - %SENDER_DISPLAY_NAME% # - %DOMAIN% Domain name as per server.name config item
# - %ROOM_NAME% # - %DOMAIN_PRETTY% Word capitalize version of the domain. e.g. example.org -> Example.org
contentPath: "/absolute/path/to/file" # - %FROM_EMAIL% Value of this section's email config item
# - %FROM_NAME% Value of this section's name config item
# - %SENDER_ID% Matrix ID of the invitation sender
# - %SENDER_NAME% Display name of the invitation sender, empty if not available
# - %SENDER_NAME_OR_ID% Value of %SENDER_NAME% or, if empty, value of %SENDER_ID%
# - %ROOM_ID% ID of the room where the invitation took place
# - %ROOM_NAME% Name of the room, empty if not available
# - %ROOM_NAME_OR_ID% Value of %ROOM_NAME% or, if empty, value of %ROOM_ID%
template: "/absolute/path/to/file"

View File

@@ -42,7 +42,7 @@ public class EmailSenderConfig {
private String password; private String password;
private String email; private String email;
private String name; private String name;
private String contentPath; private String template;
public String getHost() { public String getHost() {
return host; return host;
@@ -100,12 +100,12 @@ public class EmailSenderConfig {
this.name = name; this.name = name;
} }
public String getContentPath() { public String getTemplate() {
return contentPath; return template;
} }
public void setContentPath(String contentPath) { public void setTemplate(String template) {
this.contentPath = contentPath; this.template = template;
} }
@PostConstruct @PostConstruct
@@ -117,15 +117,19 @@ public class EmailSenderConfig {
log.info("Login: {}", getLogin()); log.info("Login: {}", getLogin());
log.info("Has password: {}", StringUtils.isBlank(getPassword())); log.info("Has password: {}", StringUtils.isBlank(getPassword()));
log.info("E-mail: {}", getEmail()); log.info("E-mail: {}", getEmail());
if (StringUtils.isBlank(getContentPath())) { if (!StringUtils.startsWith(getTemplate(), "classpath:")) {
log.warn("invite.sender.contentPath is empty! Will not send invites"); if (StringUtils.isBlank(getTemplate())) {
log.warn("invite.sender.template is empty! Will not send invites");
} else { } else {
File cp = new File(getContentPath()).getAbsoluteFile(); File cp = new File(getTemplate()).getAbsoluteFile();
log.info("Content path: {}", cp.getAbsolutePath()); log.info("Template: {}", cp.getAbsolutePath());
if (!cp.exists() || !cp.isFile() || !cp.canRead()) { if (!cp.exists() || !cp.isFile() || !cp.canRead()) {
log.warn(getContentPath() + " does not exist, is not a file or cannot be read"); log.warn(getTemplate() + " does not exist, is not a file or cannot be read");
} }
} }
} else {
log.info("Template: Built-in");
}
} }
} }

View File

@@ -137,7 +137,7 @@ public class InvitationManager {
ThreePid pid = new ThreePid(invitation.getMedium(), invitation.getAddress()); ThreePid pid = new ThreePid(invitation.getMedium(), invitation.getAddress());
log.info("Storing invite for {}:{} from {} in room {}", pid.getMedium(), pid.getAddress(), invitation.getSender(), invitation.getRoomId()); log.info("Handling invite for {}:{} from {} in room {}", pid.getMedium(), pid.getAddress(), invitation.getSender(), invitation.getRoomId());
if (invitations.containsKey(pid)) { if (invitations.containsKey(pid)) {
log.info("Invite is already pending for {}:{}, returning data", pid.getMedium(), pid.getAddress()); log.info("Invite is already pending for {}:{}, returning data", pid.getMedium(), pid.getAddress());
return invitations.get(pid); return invitations.get(pid);

View File

@@ -22,13 +22,17 @@ package io.kamax.mxisd.invitation.sender;
import com.sun.mail.smtp.SMTPTransport; import com.sun.mail.smtp.SMTPTransport;
import io.kamax.matrix.ThreePidMedium; import io.kamax.matrix.ThreePidMedium;
import io.kamax.mxisd.config.ServerConfig;
import io.kamax.mxisd.config.invite.sender.EmailSenderConfig; import io.kamax.mxisd.config.invite.sender.EmailSenderConfig;
import io.kamax.mxisd.exception.ConfigurationException; import io.kamax.mxisd.exception.ConfigurationException;
import io.kamax.mxisd.invitation.IThreePidInviteReply; import io.kamax.mxisd.invitation.IThreePidInviteReply;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@@ -37,7 +41,6 @@ import javax.mail.MessagingException;
import javax.mail.Session; import javax.mail.Session;
import javax.mail.internet.InternetAddress; import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMessage;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@@ -51,6 +54,12 @@ public class EmailInviteSender implements IInviteSender {
@Autowired @Autowired
private EmailSenderConfig cfg; private EmailSenderConfig cfg;
@Autowired
private ServerConfig srvCfg;
@Autowired
private ApplicationContext app;
private Session session; private Session session;
private InternetAddress sender; private InternetAddress sender;
@@ -77,16 +86,30 @@ public class EmailInviteSender implements IInviteSender {
} }
try { try {
String templateBody = IOUtils.toString(new FileInputStream(cfg.getContentPath()), StandardCharsets.UTF_8); String domainPretty = WordUtils.capitalizeFully(srvCfg.getName());
templateBody = String senderName = invite.getInvite().getProperties().getOrDefault("sender_display_name", "");
templateBody.replace("%SENDER_DISPLAY_NAME%", invite.getInvite().getProperties().get("sender_display_name")) String senderNameOrId = StringUtils.defaultIfBlank(senderName, invite.getInvite().getSender().getId());
.replace("%ROOM_NAME%", invite.getInvite().getProperties().get("room_name")); String roomName = invite.getInvite().getProperties().getOrDefault("room_name", "");
String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getInvite().getRoomId());
String templateBody = IOUtils.toString(app.getResource(cfg.getTemplate()).getInputStream(), StandardCharsets.UTF_8);
templateBody = templateBody.replace("%DOMAIN%", srvCfg.getName());
templateBody = templateBody.replace("%DOMAIN_PRETTY%", domainPretty);
templateBody = templateBody.replace("%FROM_EMAIL%", cfg.getEmail());
templateBody = templateBody.replace("%FROM_NAME%", cfg.getName());
templateBody = templateBody.replace("%SENDER_ID%", invite.getInvite().getSender().getId());
templateBody = templateBody.replace("%SENDER_NAME%", senderName);
templateBody = templateBody.replace("%SENDER_NAME_OR_ID%", senderNameOrId);
templateBody = templateBody.replace("%ROOM_ID%", invite.getInvite().getRoomId());
templateBody = templateBody.replace("%ROOM_NAME%", roomName);
templateBody = templateBody.replace("%ROOM_NAME_OR_ID%", roomNameOrId);
MimeMessage msg = new MimeMessage(session, IOUtils.toInputStream(templateBody, StandardCharsets.UTF_8)); MimeMessage msg = new MimeMessage(session, IOUtils.toInputStream(templateBody, StandardCharsets.UTF_8));
msg.setHeader("X-Mailer", "mxisd"); // TODO set version msg.setHeader("X-Mailer", "mxisd"); // TODO set version
msg.setSentDate(new Date()); msg.setSentDate(new Date());
msg.setFrom(sender); msg.setFrom(sender);
msg.setRecipients(Message.RecipientType.TO, invite.getInvite().getAddress()); msg.setRecipients(Message.RecipientType.TO, invite.getInvite().getAddress());
msg.saveChanges();
log.info("Sending invite to {} via SMTP using {}:{}", invite.getInvite().getAddress(), cfg.getHost(), cfg.getPort()); log.info("Sending invite to {} via SMTP using {}:{}", invite.getInvite().getAddress(), cfg.getHost(), cfg.getPort());
SMTPTransport transport = (SMTPTransport) session.getTransport("smtp"); SMTPTransport transport = (SMTPTransport) session.getTransport("smtp");

View File

@@ -1,3 +1,11 @@
logging:
level:
org:
springframework: "WARN"
apache:
catalina: "WARN"
directory: "WARN"
server: server:
port: 8090 port: 8090
@@ -18,16 +26,17 @@ lookup:
ldap: ldap:
enabled: false enabled: false
firebase:
enabled: false
forward: forward:
servers: servers:
- "https://matrix.org" - "https://matrix.org"
- "https://vector.im" - "https://vector.im"
firebase:
enabled: false
invite: invite:
sender: sender:
email: email:
tls: 1 tls: 1
name: "mxisd Identity Server" name: "mxisd Identity Server"
template: "classpath:email/invite-template.eml"

View File

@@ -0,0 +1,91 @@
Subject: You have been invited to %DOMAIN%
MIME-Version: 1.0
Content-Type: multipart/alternative;
boundary="7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ"
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
Content-Type: text/plain; charset=UTF-8
Content-Disposition: inline
Hi,
%SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on
Matrix. To join the conversation, register an account on http://%DOMAIN%
About Matrix:
Matrix is an open standard for interoperable, decentralised, real-time communication
over IP, supporting group chat, file transfer, voice and video calling, integrations to
other apps, bridges to other communication systems and much more. It can be used to power
Instant Messaging, VoIP/WebRTC signalling, Internet of Things communication.
Thanks,
%DOMAIN_PRETTY% Admins
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
Content-Type: multipart/related;
boundary="M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR";
type="text/html"
--M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR
Content-Type: text/html; charset=UTF-8
Content-Disposition: inline
<!doctype html>
<html lang="en">
<head>
<style type="text/css">
body {
margin: 0px;
}
pre, code {
word-break: break-word;
white-space: pre-wrap;
}
#page {
font-family: 'Open Sans', Helvetica, Arial, Sans-Serif;
font-color: #454545;
font-size: 12pt;
width: 100%%;
padding: 20px;
}
#inner {
width: 640px;
}
</style>
</head>
<body>
<table id="page">
<tr>
<td> </td>
<td id="inner">
<p>Hi,</p>
<p>%SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on
Matrix. To join the conversation, register an account on <a href="http://%DOMAIN%">%DOMAIN%</a>.</p>
<br>
<p>About Matrix:</p>
<p>Matrix is an open standard for interoperable, decentralised, real-time communication
over IP, supporting group chat, file transfer, voice and video calling, integrations to
other apps, bridges to other communication systems and much more. It can be used to power
Instant Messaging, VoIP/WebRTC signalling, Internet of Things communication.</p>
<p>Thanks,</p>
<p>%DOMAIN_PRETTY% Admins</p>
</td>
<td> </td>
</tr>
</table>
</body>
</html>
--M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR--
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ--