diff --git a/src/main/java/org/olat/core/commons/modules/bc/meta/_content/external_url.html b/src/main/java/org/olat/core/commons/modules/bc/meta/_content/external_url.html
index 247750d5f24c7780901db413030337679b6e6081..9afbc241bc92035cf824e6c8cca17a57400bacae 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/meta/_content/external_url.html
+++ b/src/main/java/org/olat/core/commons/modules/bc/meta/_content/external_url.html
@@ -1,8 +1,6 @@
 <div class="o_copy_code o_nowrap form-control-static"><a href="javascript:;" id="o_extlink"><i class="o_icon o_icon-lg o_icon-fw o_icon_qrcode">&nbsp;</i></a><input type="text" value="$resourceUrl" onclick="this.select()"/></div>
 <script>
-/* <![CDATA[ */
-	jQuery(function() {
-		o_QRCodePopup('o_extlink', '$resourceUrl', 'right');
-	});
-/* ]]> */
+jQuery(function() {
+	o_QRCodePopup('o_extlink', '$resourceUrl', 'right');
+});
 </script>
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/commons/services/taskexecutor/TaskExecutorManager.java b/src/main/java/org/olat/core/commons/services/taskexecutor/TaskExecutorManager.java
index b38be44b61c0fc4b890cb2877dbf7cb3fbbac220..ad38f144512ab526ed571acc724b68714576640f 100644
--- a/src/main/java/org/olat/core/commons/services/taskexecutor/TaskExecutorManager.java
+++ b/src/main/java/org/olat/core/commons/services/taskexecutor/TaskExecutorManager.java
@@ -27,6 +27,7 @@ package org.olat.core.commons.services.taskexecutor;
 
 import java.util.Date;
 import java.util.List;
+import java.util.TimerTask;
 import java.util.concurrent.Executor;
 
 import org.olat.core.id.Identity;
@@ -101,5 +102,16 @@ public interface TaskExecutorManager extends Executor {
 	 * @param resSubPath The sub path (cannot be null)
 	 */
 	public void delete(OLATResource resource, String resSubPath);
+	
+	/**
+	 * This is a light weight, not clustered way to delay a task
+	 * a few seconds. Don't abuse of it, only delay of a few seconds
+	 * is acceptable because the tasks are serialized and the task is
+	 * hold in memory. 
+	 * 
+	 * @param task
+	 * @param delay
+	 */
+	public void schedule(TimerTask task, long delay);
 
 }
diff --git a/src/main/java/org/olat/core/commons/services/taskexecutor/manager/TaskExecutorManagerImpl.java b/src/main/java/org/olat/core/commons/services/taskexecutor/manager/TaskExecutorManagerImpl.java
index 68a6839efb14d3d75a0c02ab932938c35ff198eb..03b4c3221b25b850a57d9d00f4a5485abdf66ab4 100644
--- a/src/main/java/org/olat/core/commons/services/taskexecutor/manager/TaskExecutorManagerImpl.java
+++ b/src/main/java/org/olat/core/commons/services/taskexecutor/manager/TaskExecutorManagerImpl.java
@@ -28,10 +28,13 @@ package org.olat.core.commons.services.taskexecutor.manager;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
 import java.util.UUID;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.RejectedExecutionException;
 
+import org.apache.logging.log4j.Logger;
 import org.olat.core.commons.persistence.DB;
 import org.olat.core.commons.services.taskexecutor.LongRunnable;
 import org.olat.core.commons.services.taskexecutor.LowPriorityRunnable;
@@ -45,7 +48,6 @@ import org.olat.core.commons.services.taskexecutor.model.PersistentTask;
 import org.olat.core.commons.services.taskexecutor.model.PersistentTaskRunnable;
 import org.olat.core.id.Identity;
 import org.olat.core.logging.AssertException;
-import org.apache.logging.log4j.Logger;
 import org.olat.core.logging.Tracing;
 import org.olat.resource.OLATResource;
 import org.quartz.JobKey;
@@ -73,6 +75,8 @@ public class TaskExecutorManagerImpl implements TaskExecutorManager {
 	private DB dbInstance;
 	private Scheduler scheduler;
 	private PersistentTaskDAO persistentTaskDao;
+	
+	private Timer timer = new Timer();
 
 	/**
 	 * [used by spring]
@@ -238,6 +242,11 @@ public class TaskExecutorManagerImpl implements TaskExecutorManager {
 	public void delete(OLATResource resource, String resSubPath) {
 		persistentTaskDao.delete(resource, resSubPath);
 	}
+
+	@Override
+	public void schedule(TimerTask task, long delay) {
+		timer.schedule(task, delay);
+	}
 	
 	public enum Queue {
 		sequential,
diff --git a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonDispatcher.java b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonDispatcher.java
index a6d0c05b18e85a9537eec3fa831e3d693b286676..614884501aff6c2019e02622bd0a548e22aad863 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonDispatcher.java
+++ b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonDispatcher.java
@@ -58,9 +58,9 @@ public class BigBlueButtonDispatcher implements Dispatcher {
 
 	private static final Logger log = Tracing.createLoggerFor(BigBlueButtonDispatcher.class);
 	
-	private static final String BIGBLUEBUTTON_PATH = "survey";
+	private static final String BIGBLUEBUTTON_PATH = "bigbluebutton";
 	
-	public static final String getExecutionUrl(String identifier) {
+	public static final String getMeetingUrl(String identifier) {
 		return new StringBuilder()
 				.append(Settings.getServerContextPathURI())
 				.append("/")
@@ -72,8 +72,6 @@ public class BigBlueButtonDispatcher implements Dispatcher {
 	
 	@Override
 	public void execute(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
-		
-		
 		UserRequest ureq = null;
 		final String pathInfo = request.getPathInfo();
 		String uriPrefix = DispatcherModule.getLegacyUriPrefix(request);
diff --git a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonManager.java b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonManager.java
index 4c9b1d777ce4059fdb13ba2e8617b9854278ac2c..fe714ec51fd3558fe6c725238104a42ff3618a8a 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonManager.java
+++ b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonManager.java
@@ -80,6 +80,13 @@ public interface BigBlueButtonManager {
 
 	public BigBlueButtonMeeting getMeeting(BigBlueButtonMeeting meeting);
 	
+	/**
+	 * Return the first meeting which matches the specified identifier
+	 * as the meeting's identifier or readable identifier.
+	 * 
+	 * @param identifier The identifier
+	 * @return A meeting
+	 */
 	public BigBlueButtonMeeting getMeeting(String identifier);
 	
 	public BigBlueButtonMeeting updateMeeting(BigBlueButtonMeeting meeting);
@@ -87,6 +94,8 @@ public interface BigBlueButtonManager {
 	public boolean deleteMeeting(BigBlueButtonMeeting meeting, BigBlueButtonErrors errors);
 	
 	public BigBlueButtonMeetingTemplate createAndPersistTemplate(String name);
+	
+	public boolean isIdentifierInUse(String identifier, BigBlueButtonMeeting reference);
 
 	public List<BigBlueButtonMeetingTemplate> getTemplates();
 	
diff --git a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonMeetingTemplate.java b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonMeetingTemplate.java
index 78f4bd411681e456ab071f50dcb26d169de35440..03fbaf10eea6ac007ebc8734b639ec6d6bace0f5 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonMeetingTemplate.java
+++ b/src/main/java/org/olat/modules/bigbluebutton/BigBlueButtonMeetingTemplate.java
@@ -50,6 +50,10 @@ public interface BigBlueButtonMeetingTemplate extends ModifiedInfo, CreateInfo {
 
 	public void setDescription(String description);
 	
+	public boolean isExternalUsersAllowed();
+	
+	public void setExternalUsersAllowed(boolean external);
+	
 	public Integer getMaxConcurrentMeetings();
 
 	public void setMaxConcurrentMeetings(Integer maxConcurrentMeetings);
diff --git a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonManagerImpl.java b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonManagerImpl.java
index 3ff2815531637a04d88712d53ca86ddf4af3e51e..730b7962f0e4ab9c8134f87a070c6fe95f43739c 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonManagerImpl.java
+++ b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonManagerImpl.java
@@ -336,6 +336,14 @@ public class BigBlueButtonManagerImpl implements BigBlueButtonManager,
 		return null;
 	}
 
+	@Override
+	public boolean isIdentifierInUse(String identifier, BigBlueButtonMeeting reference) {
+		if(StringHelper.containsNonWhitespace(identifier)) {
+			return bigBlueButtonMeetingDao.isIdentifierInUse(identifier, reference);
+		}
+		return false;
+	}
+
 	@Override
 	public BigBlueButtonMeeting updateMeeting(BigBlueButtonMeeting meeting) {
 		updateCalendarEvent(meeting);
diff --git a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAO.java b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAO.java
index b98b2de52839b7d17738a8cf871407fc9183ad2b..8f3d37fe97c8f799788a05c570066b25c130d088 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAO.java
+++ b/src/main/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAO.java
@@ -99,7 +99,7 @@ public class BigBlueButtonMeetingDAO {
 		  .append(" left join fetch meeting.businessGroup as businessGroup")
 		  .append(" left join fetch meeting.template as template")
 		  .append(" left join fetch meeting.server as server")
-		  .append(" where meeting.identifier=:identifier");
+		  .append(" where meeting.identifier=:identifier or meeting.readableIdentifier=:identifier");
 		
 		List<BigBlueButtonMeeting> meetings = dbInstance.getCurrentEntityManager()
 				.createQuery(sb.toString(), BigBlueButtonMeeting.class)
@@ -108,6 +108,27 @@ public class BigBlueButtonMeetingDAO {
 		return meetings == null || meetings.isEmpty() ? null : meetings.get(0);
 	}
 	
+	public boolean isIdentifierInUse(String identifier, BigBlueButtonMeeting reference) {
+		QueryBuilder sb = new QueryBuilder();
+		sb.append("select meeting.key from bigbluebuttonmeeting as meeting")
+		  .append(" where (meeting.identifier=:identifier or meeting.readableIdentifier=:identifier)");
+		if(reference != null) {
+			sb.append(" and meeting.key<>:referenceKey");
+		}
+		
+		TypedQuery<Long> query = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Long.class)
+				.setParameter("identifier", identifier)
+				.setFirstResult(0)
+				.setMaxResults(1);
+		if(reference != null) {
+			query.setParameter("referenceKey", reference.getKey());
+		}
+		
+		List<Long> otherKeys = query.getResultList();
+		return otherKeys != null && !otherKeys.isEmpty() && otherKeys.get(0) != null && otherKeys.get(0).intValue() > 0;
+	}
+	
 	public List<String> getMeetingsIds(Date from, Date to ) {
 		QueryBuilder sb = new QueryBuilder();
 		sb.append("select meeting.meetingId from bigbluebuttonmeeting as meeting")
diff --git a/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonMeetingTemplateImpl.java b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonMeetingTemplateImpl.java
index fa8019d77dac130f55ebd53ecd0f478fb1975f65..98bddddff713e76d3439f0f595d5600e041544b8 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonMeetingTemplateImpl.java
+++ b/src/main/java/org/olat/modules/bigbluebutton/model/BigBlueButtonMeetingTemplateImpl.java
@@ -75,6 +75,9 @@ public class BigBlueButtonMeetingTemplateImpl implements Persistable, BigBlueBut
 	
 	@Column(name="b_max_concurrent_meetings", nullable=true, insertable=true, updatable=true)
 	private Integer maxConcurrentMeetings;
+
+	@Column(name="b_external_users", nullable=false, insertable=true, updatable=true)
+	private boolean externalAllowed;
 	
 	@Column(name="b_max_participants", nullable=true, insertable=true, updatable=true)
 	private Integer maxParticipants;
@@ -200,6 +203,16 @@ public class BigBlueButtonMeetingTemplateImpl implements Persistable, BigBlueBut
 		this.externalId = externalId;
 	}
 
+	@Override
+	public boolean isExternalUsersAllowed() {
+		return externalAllowed;
+	}
+
+	@Override
+	public void setExternalUsersAllowed(boolean externalAllowed) {
+		this.externalAllowed = externalAllowed;
+	}
+
 	@Override
 	public Integer getMaxConcurrentMeetings() {
 		return maxConcurrentMeetings;
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminTemplatesController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminTemplatesController.java
index 2924526e8a6bc4acd054224ab069e8e4d2b4299a..dd39809d3f2b7f9618ba471751eafb11441d6f45 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminTemplatesController.java
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonAdminTemplatesController.java
@@ -89,6 +89,7 @@ public class BigBlueButtonAdminTemplatesController extends FormBasicController {
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(BTemplatesCols.maxDuration,
 				new TemplateMinuteCellRenderer(getTranslator())));
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(BTemplatesCols.webcamsOnlyForModerator));
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(BTemplatesCols.externalUsers));
 		if(readOnly) {
 			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("view", translate("view"), "view"));
 		} else {
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonGuestJoinController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonGuestJoinController.java
index 0927881cd5e7c4d3e3752f8e18afaec55eb66b00..28755a74d9f7fb674da9782daf52355c176513c5 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonGuestJoinController.java
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonGuestJoinController.java
@@ -28,14 +28,16 @@ import org.olat.core.gui.components.form.flexible.elements.FormLink;
 import org.olat.core.gui.components.form.flexible.elements.TextElement;
 import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
 import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
 import org.olat.core.gui.components.link.Link;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.media.MediaResource;
 import org.olat.core.gui.media.RedirectMediaResource;
+import org.olat.core.id.IdentityEnvironment;
 import org.olat.core.id.OLATResourceable;
-import org.olat.core.id.Roles;
+import org.olat.core.util.Formatter;
 import org.olat.core.util.StringHelper;
 import org.olat.core.util.UserSession;
 import org.olat.core.util.coordinate.CoordinatorManager;
@@ -57,6 +59,7 @@ import org.olat.modules.bigbluebutton.BigBlueButtonModule;
 import org.olat.modules.bigbluebutton.model.BigBlueButtonErrors;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryEntrySecurity;
+import org.olat.repository.RepositoryEntryStatusEnum;
 import org.olat.repository.RepositoryManager;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -77,7 +80,7 @@ public class BigBlueButtonGuestJoinController extends FormBasicController implem
 	private boolean moderatorStartMeeting;
 	private final boolean allowedToMeet;
 	private BigBlueButtonMeeting meeting;
-	private final OLATResourceable meetingOres;
+	private OLATResourceable meetingOres;
 	
 	@Autowired
 	private NodeAccessService nodeAccessService;
@@ -98,21 +101,42 @@ public class BigBlueButtonGuestJoinController extends FormBasicController implem
 		initForm(ureq);
 		updateButtonsAndStatus();
 
-		meetingOres = OresHelper.createOLATResourceableInstance(BigBlueButtonMeeting.class.getSimpleName(), meeting.getKey());
-		CoordinatorManager.getInstance().getCoordinator().getEventBus().registerFor(this, getIdentity(), meetingOres);
+		if(meeting != null) {
+			meetingOres = OresHelper.createOLATResourceableInstance(BigBlueButtonMeeting.class.getSimpleName(), meeting.getKey());
+			CoordinatorManager.getInstance().getCoordinator().getEventBus().registerFor(this, getIdentity(), meetingOres);
+		}
 	}
 
 	@Override
 	protected void doDispose() {
-		CoordinatorManager.getInstance().getCoordinator().getEventBus().deregisterFor(this, meetingOres);
+		if(meetingOres != null) {
+			CoordinatorManager.getInstance().getCoordinator().getEventBus().deregisterFor(this, meetingOres);
+		}
 	}
 
 	@Override
 	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
-		boolean end = isEnded();
+
+		if(formLayout instanceof FormLayoutContainer && meeting != null
+				&& !Boolean.TRUE.equals(ureq.getUserSession().getEntry("meeting-" + meeting.getKey()))) {
+			FormLayoutContainer layoutCont = (FormLayoutContainer)formLayout;
+			layoutCont.contextPut("title", meeting.getName());
+			if(StringHelper.containsNonWhitespace(meeting.getDescription())) {
+				layoutCont.contextPut("description", meeting.getDescription());
+			}
+			if(meeting.getStartDate() != null) {
+				String start = Formatter.getInstance(getLocale()).formatDateAndTime(meeting.getStartDate());
+				layoutCont.contextPut("start", start);
+			}
+			if(meeting.getEndDate() != null) {
+				String end = Formatter.getInstance(getLocale()).formatDateAndTime(meeting.getEndDate());
+				layoutCont.contextPut("end", end);
+			}
+		}
 		
 		nameEl = uifactory.addTextElement("meeting.guest.pseudo", 128, "", formLayout);
 		
+		boolean end = isEnded();
 		joinButton = uifactory.addFormLink("meeting.join.button", formLayout, Link.BUTTON_LARGE);
 		joinButton.setElementCssClass("o_sel_bbb_guest_join");
 		joinButton.setVisible(!end);
@@ -156,16 +180,23 @@ public class BigBlueButtonGuestJoinController extends FormBasicController implem
 	private boolean isAllowedToMeet(UserRequest ureq) {
 		if(meeting == null) return false;
 
-		if(meeting.getEntry() != null) {
-			UserSession usess = ureq.getUserSession();
-			Roles roles = usess.getRoles();
-			RepositoryEntrySecurity reSecurity = repositoryManager.isAllowed(getIdentity(), roles, meeting.getEntry());
+		UserSession usess = ureq.getUserSession();
+		IdentityEnvironment identEnv = usess.getIdentityEnvironment();
+		if(identEnv.getRoles() == null && identEnv.getIdentity() == null) {
+			boolean externalUsersAllowed = StringHelper.containsNonWhitespace(meeting.getReadableIdentifier());
+			if(meeting.getEntry() != null) {
+				RepositoryEntry re = meeting.getEntry();
+				externalUsersAllowed &= re.getEntryStatus() == RepositoryEntryStatusEnum.published;
+			}
+			return externalUsersAllowed;
+		} else if(meeting.getEntry() != null) {
+			RepositoryEntrySecurity reSecurity = repositoryManager.isAllowed(getIdentity(), identEnv.getRoles(), meeting.getEntry());
 			if(reSecurity.canLaunch()) {
 				readOnly = reSecurity.isReadOnly();
 				if(StringHelper.containsNonWhitespace(meeting.getSubIdent())) {
 					RepositoryEntry entry = repositoryManager.lookupRepositoryEntry(meeting.getEntry().getKey());
 					ICourse course = CourseFactory.loadCourse(entry);
-					UserCourseEnvironmentImpl uce = new UserCourseEnvironmentImpl(usess.getIdentityEnvironment(), course.getCourseEnvironment());
+					UserCourseEnvironmentImpl uce = new UserCourseEnvironmentImpl(identEnv, course.getCourseEnvironment());
 					CourseTreeNode courseTreeNode = (CourseTreeNode)nodeAccessService.getCourseTreeModelBuilder(uce)
 							.withFilter(AccessibleFilter.create())
 							.build()
@@ -181,6 +212,7 @@ public class BigBlueButtonGuestJoinController extends FormBasicController implem
 	}
 	
 	private boolean isModeratorStartMeeting() {
+		if(meeting == null) return true;
 		if(meeting.getEntry() != null) {
 			RepositoryEntry entry = repositoryManager.lookupRepositoryEntry(meeting.getEntry().getKey());
 			ICourse course = CourseFactory.loadCourse(entry);
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingController.java
index e0b26b50322e48bf4ca30e00eded6afda1d57a98..26606b0fe476a3922c95f54dca7cd4a213ad3b2f 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingController.java
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonMeetingController.java
@@ -21,7 +21,9 @@ package org.olat.modules.bigbluebutton.ui;
 
 import java.util.Date;
 import java.util.List;
+import java.util.TimerTask;
 
+import org.olat.core.commons.services.taskexecutor.TaskExecutorManager;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
 import org.olat.core.gui.components.form.flexible.FormItem;
@@ -48,9 +50,11 @@ import org.olat.core.helpers.Settings;
 import org.olat.core.id.OLATResourceable;
 import org.olat.core.util.Formatter;
 import org.olat.core.util.StringHelper;
+import org.olat.core.util.UserSession;
 import org.olat.core.util.coordinate.CoordinatorManager;
 import org.olat.core.util.event.GenericEventListener;
 import org.olat.core.util.resource.OresHelper;
+import org.olat.modules.bigbluebutton.BigBlueButtonDispatcher;
 import org.olat.modules.bigbluebutton.BigBlueButtonManager;
 import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
 import org.olat.modules.bigbluebutton.BigBlueButtonModule;
@@ -83,6 +87,8 @@ public class BigBlueButtonMeetingController extends FormBasicController implemen
 	
 	private DialogBoxController confirmDeleteRecordingDialog;
 
+	@Autowired
+	private TaskExecutorManager taskExecutorManager;
 	@Autowired
 	private BigBlueButtonModule bigBlueButtonModule;
 	@Autowired
@@ -96,7 +102,8 @@ public class BigBlueButtonMeetingController extends FormBasicController implemen
 		this.readOnly = readOnly;
 		this.moderator = moderator;
 		this.administrator = administrator;
-		guest = ureq.getUserSession().getRoles().isGuestOnly();
+		UserSession usess = ureq.getUserSession();
+		guest = usess.getRoles().isGuestOnly();
 		meetingOres = OresHelper.createOLATResourceableInstance(BigBlueButtonMeeting.class.getSimpleName(), meeting.getKey());
 		CoordinatorManager.getInstance().getCoordinator().getEventBus().registerFor(this, getIdentity(), meetingOres);
 		moderatorStartMeeting = configuration.isModeratorStartMeeting();
@@ -104,6 +111,10 @@ public class BigBlueButtonMeetingController extends FormBasicController implemen
 		initForm(ureq);
 		updateButtonsAndStatus();
 		loadRecordingsModel();
+		
+		if(guest) {
+			usess.putEntryInNonClearedStore("meeting-" + meeting.getKey(), Boolean.TRUE);
+		}
 	}
 	
 	@Override
@@ -128,6 +139,11 @@ public class BigBlueButtonMeetingController extends FormBasicController implemen
 				String end = Formatter.getInstance(getLocale()).formatDateAndTime(meeting.getEndDate());
 				layoutCont.contextPut("end", end);
 			}
+			
+			if((administrator || moderator) && StringHelper.containsNonWhitespace(meeting.getReadableIdentifier())) {
+				String url = BigBlueButtonDispatcher.getMeetingUrl(meeting.getReadableIdentifier());
+				layoutCont.contextPut("externalUrl", url);
+			}
 		}
 		
 		joinButton = LinkFactory.createButtonLarge("meeting.join.button", flc.getFormItemComponent(), this);
@@ -308,8 +324,7 @@ public class BigBlueButtonMeetingController extends FormBasicController implemen
 		BigBlueButtonErrors errors = new BigBlueButtonErrors();
 		if(moderator || administrator) {
 			meetingUrl = bigBlueButtonManager.join(meeting, getIdentity(), null, (administrator || moderator), false, null, errors);
-			BigBlueButtonEvent openEvent = new BigBlueButtonEvent(meeting.getKey(), getIdentity().getKey());
-        	CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(openEvent, meetingOres);
+			delayEvent(new BigBlueButtonEvent(meeting.getKey(), getIdentity().getKey()));
 		} else if(!moderatorStartMeeting) {
 			meetingUrl = bigBlueButtonManager.join(meeting, getIdentity(), null, false, guest, null, errors);
 		} else if(bigBlueButtonManager.isMeetingRunning(meeting)) {
@@ -344,4 +359,25 @@ public class BigBlueButtonMeetingController extends FormBasicController implemen
 		}
 		loadRecordingsModel();
 	}
+	
+	private void delayEvent(BigBlueButtonEvent openEvent) {
+		final EventTask task = new EventTask(openEvent, meetingOres);
+		taskExecutorManager.schedule(task , 10000);
+	}
+	
+	private static class EventTask extends TimerTask {
+		
+		private final BigBlueButtonEvent event;
+		private final OLATResourceable ores;
+		
+		public EventTask(BigBlueButtonEvent event, OLATResourceable ores) {
+			this.event = event;
+			this.ores = OresHelper.clone(ores);
+		}
+
+		@Override
+		public void run() {
+        	CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(event, ores);
+		}
+	}
 }
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonTemplateTableModel.java b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonTemplateTableModel.java
index eb48b4f28b991c5c71d22067ca6fa86627b9f7ca..47146431ce4ef8a254d8d420daa4da6e1a7960f9 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonTemplateTableModel.java
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/BigBlueButtonTemplateTableModel.java
@@ -72,6 +72,7 @@ implements SortableFlexiTableDataModel<BigBlueButtonMeetingTemplate> {
 			case maxParticipants: return row.getMaxParticipants();
 			case maxDuration: return row.getMaxDuration();
 			case webcamsOnlyForModerator: return row.getWebcamsOnlyForModerator();
+			case externalUsers: return row.isExternalUsersAllowed();
 			default: return "ERROR";
 		}
 	}
@@ -89,7 +90,8 @@ implements SortableFlexiTableDataModel<BigBlueButtonMeetingTemplate> {
 		maxConcurrentMeetings("table.header.max.concurrent.meetings"),
 		maxParticipants("table.header.max.participants"),
 		maxDuration("table.header.max.duration"),
-		webcamsOnlyForModerator("table.header.webcams.only.moderator");
+		webcamsOnlyForModerator("table.header.webcams.only.moderator"),
+		externalUsers("table.header.external.users");
 		
 		private final String i18nHeaderKey;
 		
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/EditBigBlueButtonMeetingController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/EditBigBlueButtonMeetingController.java
index d66adcba1fe18db93513b1c75d6a9140c9360169..8ecb297cc79cea9b1c5cef1a7109e72ff7010803 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/ui/EditBigBlueButtonMeetingController.java
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/EditBigBlueButtonMeetingController.java
@@ -19,6 +19,7 @@
  */
 package org.olat.modules.bigbluebutton.ui;
 
+import java.net.URI;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.List;
@@ -41,6 +42,7 @@ import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
 import org.olat.core.util.StringHelper;
 import org.olat.group.BusinessGroup;
+import org.olat.modules.bigbluebutton.BigBlueButtonDispatcher;
 import org.olat.modules.bigbluebutton.BigBlueButtonManager;
 import org.olat.modules.bigbluebutton.BigBlueButtonMeeting;
 import org.olat.modules.bigbluebutton.BigBlueButtonMeetingLayoutEnum;
@@ -71,6 +73,7 @@ public class EditBigBlueButtonMeetingController extends FormBasicController {
 	private SingleSelection templateEl;
 	private SingleSelection layoutEl;
 	private MultipleSelectionElement guestEl;
+	private TextElement externalLinkEl;
 
 	private final Mode mode;
 	private final String subIdent;
@@ -201,7 +204,10 @@ public class EditBigBlueButtonMeetingController extends FormBasicController {
 		String[] guestValues = new String[] { translate("meeting.guest.on") };
 		guestEl = uifactory.addCheckboxesHorizontal("meeting.guest", formLayout, onKeys, guestValues);
 		guestEl.setVisible(entry != null && entry.isGuests());
-	
+		
+		String externalLink = meeting == null ? null : meeting.getReadableIdentifier();
+		externalLinkEl = uifactory.addTextElement("meeting.external.users", 64, externalLink, formLayout);
+		
 		openCalLink = uifactory.addFormLink("calendar.open", formLayout);
 		openCalLink.setIconLeftCSS("o_icon o_icon-fw o_icon_calendar");
 		updateTemplateInformations();
@@ -269,6 +275,9 @@ public class EditBigBlueButtonMeetingController extends FormBasicController {
 					templateEl.setExampleKey("template.explain.max.participants", args);
 				}
 			}
+			externalLinkEl.setVisible(template != null && template.isExternalUsersAllowed());
+		} else {
+			externalLinkEl.setVisible(false);
 		}
 	}
 	
@@ -312,6 +321,8 @@ public class EditBigBlueButtonMeetingController extends FormBasicController {
 	@Override
 	public boolean validateFormLogic(UserRequest ureq) {
 		boolean allOk = super.validateFormLogic(ureq);
+		
+		allOk &= validateReadableIdentifier();
 
 		if(mode == Mode.dates) {
 			startDateEl.clearError();
@@ -371,6 +382,32 @@ public class EditBigBlueButtonMeetingController extends FormBasicController {
 		return allOk;
 	}
 	
+	private boolean validateReadableIdentifier() {
+		boolean allOk = true;
+		
+		externalLinkEl.clearError();
+		if(externalLinkEl.isVisible() && StringHelper.containsNonWhitespace(externalLinkEl.getValue())) {
+			String identifier = externalLinkEl.getValue();
+			if(identifier.length() > 64) {
+				externalLinkEl.setErrorKey("form.error.toolong", new String[] { "64" });
+				allOk &= false;
+			} else if(bigBlueButtonManager.isIdentifierInUse(identifier, meeting)) {
+				externalLinkEl.setErrorKey("error.identifier.in.use", null);
+				allOk &= false;
+			} else {
+				try {
+					URI uri = new URI(BigBlueButtonDispatcher.getMeetingUrl(identifier));
+					uri.normalize();
+				} catch(Exception e) {
+					externalLinkEl.setErrorKey("error.identifier.url.not.valid", new String[] { e.getMessage() });
+					allOk &= false;
+				}
+			}
+		}
+
+		return allOk;
+	}
+	
 	private boolean validateTime(TextElement el, long maxValue) {
 		boolean allOk = true;
 		el.clearError();
@@ -505,6 +542,13 @@ public class EditBigBlueButtonMeetingController extends FormBasicController {
 		BigBlueButtonMeetingTemplate template = getSelectedTemplate();
 		meeting.setTemplate(template);
 		
+		if(template != null && template.isExternalUsersAllowed()
+				&& externalLinkEl.isVisible() && StringHelper.containsNonWhitespace(externalLinkEl.getValue())) {
+			meeting.setReadableIdentifier(externalLinkEl.getValue());
+		} else {
+			meeting.setReadableIdentifier(null);
+		}
+		
 		meeting.setPermanent(mode == Mode.permanent);
 		if(mode == Mode.permanent) {
 			meeting.setStartDate(null);
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/EditBigBlueButtonTemplateController.java b/src/main/java/org/olat/modules/bigbluebutton/ui/EditBigBlueButtonTemplateController.java
index 1989a106e5f6a87c6b492ad0617ef995dc9859c4..70742cba60e8f0ef2ad313c4dd2d77132d0f2fbd 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/ui/EditBigBlueButtonTemplateController.java
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/EditBigBlueButtonTemplateController.java
@@ -59,6 +59,7 @@ public class EditBigBlueButtonTemplateController extends FormBasicController {
 	
 	private MultipleSelectionElement enableEl;
 	private MultipleSelectionElement rolesEl;
+	private MultipleSelectionElement externalEl;
 	
 	private TextElement maxConcurrentMeetingsEl;
 	private TextElement maxParticipantsEl;
@@ -141,6 +142,10 @@ public class EditBigBlueButtonTemplateController extends FormBasicController {
 		String maxConcurrentMeetings = template == null || template.getMaxConcurrentMeetings() == null ? "" : template.getMaxConcurrentMeetings().toString();
 		maxConcurrentMeetingsEl = uifactory.addTextElement("template.max.concurrent.meetings", "template.max.concurrent.meetings", 8, maxConcurrentMeetings, formLayout);
 		
+		boolean external = template != null && template.isExternalUsersAllowed();
+		externalEl = uifactory.addCheckboxesHorizontal("template.external.enabled", "template.external.enabled", formLayout, onKeys, new String[] { "" });
+		externalEl.select(onKeys[0], external);
+		
 		KeyValues rolesKeyValues = new KeyValues();
 		for(BigBlueButtonTemplatePermissions role:BigBlueButtonTemplatePermissions.values()) {
 			rolesKeyValues.add(KeyValues.entry(role.name(), translate("role.".concat(role.name()))));
@@ -353,6 +358,7 @@ public class EditBigBlueButtonTemplateController extends FormBasicController {
 		}
 		template.setDescription(descriptionEl.getValue());
 		template.setEnabled(enableEl.isAtLeastSelected(1));
+		template.setExternalUsersAllowed(externalEl.isAtLeastSelected(1));
 		
 		List<BigBlueButtonTemplatePermissions> roles = rolesEl.getSelectedKeys().stream()
 				.map(BigBlueButtonTemplatePermissions::valueOf).collect(Collectors.toList());
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/_content/guest_join.html b/src/main/java/org/olat/modules/bigbluebutton/ui/_content/guest_join.html
index 3945a0ec6c2fa976fe19d8a15a0fe92a83b880c0..ccc3dbc5c0db37e60f42cd300873c81dae7d8041 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/ui/_content/guest_join.html
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/_content/guest_join.html
@@ -1,5 +1,14 @@
 <div class="o_bbb_guest_join_box">
 #if($r.isTrue($allowedToMeet))
+	<h3>$r.escapeHtml($title)</h3>
+	
+	#if($r.isNotNull($start) || $r.isNotNull($end))
+	<div><i class="o_icon o_icon_lifecycle_date"> </i> #if($r.isNotNull($start))$start#end - #if($r.isNotNull($end))$end#end</div>
+	#end
+	#if($description && !${description.isEmpty()})
+		<div class="o_block_large o_info">$r.xssScan($description)</div>
+	#end
+
 	#if($r.isTrue($ended))
 		<div class="o_block_large o_warning">$r.translate("meeting.ended")</div>
 	#end
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/_content/meeting.html b/src/main/java/org/olat/modules/bigbluebutton/ui/_content/meeting.html
index 98d442c248c96eb2fec1f07dc5323d8e22218a74..6fd809d69e88f68301495b85e9aa9e969bbf3353 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/ui/_content/meeting.html
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/_content/meeting.html
@@ -9,6 +9,18 @@
 #if($description && !${description.isEmpty()})
 	<div class="o_block_large o_info">$r.xssScan($description)</div>
 #end
+#if($r.isNotEmpty($externalUrl) && $r.isFalse($ended))
+<div class="o_block_large">
+	<label forid="externalusersmeetingurl">$r.translate("meeting.url.external.users")</label>
+	<div class="o_copy_code o_nowrap form-control-static"><a href="javascript:;" id="o_extmeetingurl"><i class="o_icon o_icon-lg o_icon-fw o_icon_qrcode"> </i></a><input id="externalusersmeetingurl" type="text" value="$externalUrl" onclick="this.select()"/></div>
+	<script>
+	jQuery(function() {
+		o_QRCodePopup('o_extmeetingurl', '$externalUrl', 'right');
+	});
+	</script>
+</div>
+#end
+
 #if($r.isTrue($ended))
 	<div class="o_block_large o_warning">$r.translate("meeting.ended")</div>
 #end
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_de.properties
index 569fbb0f2fdc7731bdae27022a123fd9918c55f6..a6e2c0e5f88d8eefeca154e0992d139d97b31d65 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_de.properties
@@ -46,6 +46,8 @@ error.date.in.past=Der Termin kann sich nicht in der Vergangenheit befinden.
 error.duration=Termindauer \u00FCberschritten. Maximal Dauer\: {0} Minuten.
 error.end.past=Der Online-Termin kann nicht in Vergangenheit geplant werden.
 error.first.date.in.past=Der erste Termin kann sich nicht in der Vergangenheit befinden.
+error.identifier.in.use=Name ist schon verwendet. Bitte ein anders w\u00E4hlen.
+error.identifier.url.not.valid=Das URL wird nicht g\u00FCltig. Bitte Sonderzeichen wie $, ?, Leerschl\u00E4ge entfernen.
 error.prefix=Ein Fehler ist aufgetreten\:
 error.same.day=Sie haben schon ein Meeting an diesem Tag geplant.
 error.server.exists=Ein Server mit diesem URL existiert schon.
@@ -64,6 +66,7 @@ meeting.deleted=Das Meeting wurde erfolgreich gel\u00F6scht.
 meeting.description=Beschreibung
 meeting.end=Ende
 meeting.ended=Der Online-Termin wurde bereits beendet.
+meeting.external.users=F\u00FCr externe Benutzer
 meeting.followupTime=Nachlaufzeit (Min.)
 meeting.go.button=Zum Online-Termin Raum
 meeting.guest.on=erlauben
@@ -84,6 +87,7 @@ meeting.resource=Kontext
 meeting.start=Beginn
 meeting.start.button=Online-Termin starten
 meeting.template=Raumvorlage
+meeting.url.external.users=Link f\u00FCr externe Benutzer
 meeting.welcome=Begr\u00FCssungstext
 meetings.admin.title=Terminverwaltung
 meetings.past=Abgelaufene Online-Termine
@@ -122,6 +126,7 @@ table.header.breakout.recording.meetings=\# Breakout Recording
 table.header.capacity.factor=Kapazit\u00E4t
 table.header.day.week=Tag
 table.header.enabled=Aktiv
+table.header.external.users=Externe Benutzer
 table.header.listener.count=\# Listeners
 table.header.load=Load
 table.header.max.concurrent.meetings=R\u00E4ume
@@ -156,6 +161,7 @@ template.description=Beschreibung
 template.enabled=Raumvorlage aktivieren
 template.explain.max.participants=Max. Anzahl Teilnehmer\: {0} ({1} R\u00E4ume verf\u00FCgbar)
 template.explain.max.participants.with.webcams.mod=Max. Anzahl Teilnehmer\: {0}, nur Moderatorenkamera ({1} R\u00E4ume verf\u00FCgbar)
+template.external.enabled=Offen f\u00FCr externe Benutzer
 template.lock=F\u00FCr gesperrte Teilnehmer... 
 template.lockSettingsDisableCam=Kamera ausschalten
 template.lockSettingsDisableMic=Mikrofon ausschalten
diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_en.properties
index a30cc2fdbca731d4dc578d09c6ed3329912bbb51..1890e205a8ea7e1ca425453c958d638c21fa2ff9 100644
--- a/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_en.properties
@@ -46,6 +46,8 @@ error.date.in.past=The meeting date can not be in the past.
 error.duration=Meeting duration is too long. Maximal duration\: {0} minutes.
 error.end.past=Online-meeting cannot be planned in the past.
 error.first.date.in.past=The first meeting date can not be in the past.
+error.identifier.in.use=Name is already used. Please choose an other one.
+error.identifier.url.not.valid=The URL will not be valid. Please remove special characters like $, ? and spaces.
 error.prefix=An error happened\:
 error.same.day=You already have a meeting planed at this date.
 error.server.exists=A server with this URL already exists.
@@ -64,6 +66,7 @@ meeting.deleted=The meeting was successfully deleted.
 meeting.description=Description
 meeting.end=End date
 meeting.ended=The online-meeting has already ended.
+meeting.external.users=For external users
 meeting.followupTime=Follow-up (min.)
 meeting.go.button=Go to the onlin-meeting room
 meeting.guest.on=allowed
@@ -84,6 +87,7 @@ meeting.resource=Context
 meeting.start=Start date
 meeting.start.button=Start the online-meeting
 meeting.template=Room-template
+meeting.url.external.users=Link for external users
 meeting.welcome=Welcome message
 meetings.admin.title=Meeting management
 meetings.past=Past online-meetings
@@ -122,6 +126,7 @@ table.header.breakout.recording.meetings=\# Breakout Recording
 table.header.capacity.factor=Capacity
 table.header.day.week=Day
 table.header.enabled=Enabled
+table.header.external.users=External users
 table.header.listener.count=\# Listeners
 table.header.load=Load
 table.header.max.concurrent.meetings=Rooms
@@ -156,6 +161,7 @@ template.description=Description
 template.enabled=Enable room-template
 template.explain.max.participants=Max. number of participants\: {0} ({1} rooms available)
 template.explain.max.participants.with.webcams.mod=Max. number of participants\: {0}, only moderator webcam ({1} rooms available)
+template.external.enabled=Open for external users
 template.lock=For locked participants... 
 template.lockSettingsDisableCam=disable webcam
 template.lockSettingsDisableMic=disable microphone
diff --git a/src/main/resources/database/mysql/alter_15_0_x_to_15_1_0.sql b/src/main/resources/database/mysql/alter_15_0_x_to_15_1_0.sql
index 3c16149d635d46a7c1e00f7f9984a0372591e165..954ab5e31cbbccffb77ca4c800b45c0fc620e7ce 100644
--- a/src/main/resources/database/mysql/alter_15_0_x_to_15_1_0.sql
+++ b/src/main/resources/database/mysql/alter_15_0_x_to_15_1_0.sql
@@ -10,6 +10,8 @@ alter table o_bbb_meeting add column b_guest bool default false not null;
 alter table o_bbb_meeting add column b_identifier varchar(64);
 alter table o_bbb_meeting add column b_read_identifier varchar(64);
 
+alter table o_bbb_template add column b_external_users bool default false not null;
+
 
 -- Appointments
 create table o_ap_topic (
diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql
index 2c16d49a5dc921ab5cb6d2e4253bee3ff1f5542e..0df19af4bab0422e8cd98cff89bf0231eb04da28 100644
--- a/src/main/resources/database/mysql/setupDatabase.sql
+++ b/src/main/resources/database/mysql/setupDatabase.sql
@@ -1157,6 +1157,7 @@ create table o_bbb_template (
    b_system bool default false not null,
    b_enabled bool default true not null,
    b_external_id varchar(255) default null,
+   b_external_users bool default false not null,
    b_max_concurrent_meetings int default null,
    b_max_participants int default null,
    b_max_duration bigint default null,
diff --git a/src/main/resources/database/oracle/alter_15_0_x_to_15_1_0.sql b/src/main/resources/database/oracle/alter_15_0_x_to_15_1_0.sql
index f8b2267f45ec9b1932326b45102a85d38012359e..355cffdd8f83359ae12111fcb0fcc70dc412bd3b 100644
--- a/src/main/resources/database/oracle/alter_15_0_x_to_15_1_0.sql
+++ b/src/main/resources/database/oracle/alter_15_0_x_to_15_1_0.sql
@@ -10,6 +10,8 @@ alter table o_bbb_meeting add b_guest number default 0 not null;
 alter table o_bbb_meeting add b_identifier varchar2(64);
 alter table o_bbb_meeting add b_read_identifier varchar2(64);
 
+alter table o_bbb_template add b_external_users number default 0 not null;
+
 
 -- Appointments
 create table o_ap_topic (
diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql
index 7cebcd4e31b4c2e2ff979721a91408c7f6e808d6..5768914b3d1dcfcbbc51233f91b934db56573564 100644
--- a/src/main/resources/database/oracle/setupDatabase.sql
+++ b/src/main/resources/database/oracle/setupDatabase.sql
@@ -1220,6 +1220,7 @@ create table o_bbb_template (
    b_system number default 0 not null,
    b_enabled number default 1 not null,
    b_external_id varchar(255) default null,
+   b_external_users number default 0 not null,
    b_max_concurrent_meetings int default null,
    b_max_participants int default null,
    b_max_duration number default null,
diff --git a/src/main/resources/database/postgresql/alter_15_0_x_to_15_1_0.sql b/src/main/resources/database/postgresql/alter_15_0_x_to_15_1_0.sql
index ba13dbfd7b36baac5de690b3719a2972dca476ba..8173f9912ae22ff17aec1bd184e48f261698bbe4 100644
--- a/src/main/resources/database/postgresql/alter_15_0_x_to_15_1_0.sql
+++ b/src/main/resources/database/postgresql/alter_15_0_x_to_15_1_0.sql
@@ -10,6 +10,8 @@ alter table o_bbb_meeting add column b_guest bool default false not null;
 alter table o_bbb_meeting add column b_identifier varchar(64);
 alter table o_bbb_meeting add column b_read_identifier varchar(64);
 
+alter table o_bbb_template add column b_external_users bool default false not null;
+
 
 -- Appointments
 create table o_ap_topic (
diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql
index 6c13a5a3ca847139dced3280a405e83988265f6e..34c492ca80d283c07c3f54ddb13f6119fe57e682 100644
--- a/src/main/resources/database/postgresql/setupDatabase.sql
+++ b/src/main/resources/database/postgresql/setupDatabase.sql
@@ -1179,6 +1179,7 @@ create table o_bbb_template (
    b_system bool default false not null,
    b_enabled bool default true not null,
    b_external_id varchar(255) default null,
+   b_external_users bool default false not null,
    b_max_concurrent_meetings int default null,
    b_max_participants int8 default null,
    b_max_duration int8 default null,
diff --git a/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAOTest.java b/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAOTest.java
index d6acba287ba776cbcfac2167f9fd262d489ef251..c0d9f00cd894b0d40f356814bce8d8ab1f08edd8 100644
--- a/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAOTest.java
+++ b/src/test/java/org/olat/modules/bigbluebutton/manager/BigBlueButtonMeetingDAOTest.java
@@ -124,9 +124,8 @@ public class BigBlueButtonMeetingDAOTest extends OlatTestCase {
 	
 	@Test
 	public void loadByIdentifier() {
-		String name = "BigBlueButton - 8";
 		BusinessGroup group = businessGroupDao.createAndPersist(null, "BBB 8 group", "bbb-desc", -1, -1, false, false, false, false, false);
-		BigBlueButtonMeeting meeting = bigBlueButtonMeetingDao.createAndPersistMeeting(name, null, null, group);
+		BigBlueButtonMeeting meeting = bigBlueButtonMeetingDao.createAndPersistMeeting("BigBlueButton - 8", null, null, group);
 		dbInstance.commitAndCloseSession();
 		
 		BigBlueButtonMeeting loadedMeeting = bigBlueButtonMeetingDao.loadByIdentifier(meeting.getIdentifier());
@@ -134,6 +133,25 @@ public class BigBlueButtonMeetingDAOTest extends OlatTestCase {
 		Assert.assertEquals(meeting, loadedMeeting);
 	}
 	
+	@Test
+	public void isIdentifierInUse() {
+		BusinessGroup group = businessGroupDao.createAndPersist(null, "BBB 12 group", "bbb-desc", -1, -1, false, false, false, false, false);
+		BigBlueButtonMeeting meeting = bigBlueButtonMeetingDao.createAndPersistMeeting("BigBlueButton - 12", null, null, group);
+		BigBlueButtonMeeting meetingOther = bigBlueButtonMeetingDao.createAndPersistMeeting("BigBlueButton - 13", null, null, group);
+		dbInstance.commit();
+		String identifier = UUID.randomUUID().toString();
+		meeting.setReadableIdentifier(identifier);
+		meeting = bigBlueButtonMeetingDao.updateMeeting(meeting);
+		dbInstance.commitAndCloseSession();
+
+		boolean inUseItself = bigBlueButtonMeetingDao.isIdentifierInUse(identifier, meeting);
+		Assert.assertFalse(inUseItself);
+		boolean inUseAll = bigBlueButtonMeetingDao.isIdentifierInUse(identifier, null);
+		Assert.assertTrue(inUseAll);
+		boolean inUseOther = bigBlueButtonMeetingDao.isIdentifierInUse(identifier, meetingOther);
+		Assert.assertTrue(inUseOther);
+	}
+	
 	@Test
 	public void loadByKey() {
 		String name = "BigBlueButton - 9";