diff --git a/Dockerfile b/Dockerfile
index e5f691da331ea1499cf09b0e3d442afed196d8c4..77980a33908347a0109e3c60b99b43f4c54a4912 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,6 +8,7 @@ RUN apt-get update && apt-get install -y \
     libcrypt-openssl-rsa-perl \
     libcrypt-openssl-x509-perl \
     libdata-guid-perl \
+    libdata-random-perl \
     libdbix-class-perl \
     libdbd-pg-perl \
     libdbi-perl \
diff --git a/lib/SeMeet.pm b/lib/SeMeet.pm
index 72693194401f1a686d8703051baa63fda26304ba..1508473f39d3a94e6bc1f03c9f108270d6da17ec 100644
--- a/lib/SeMeet.pm
+++ b/lib/SeMeet.pm
@@ -127,7 +127,9 @@ sub startup( $self ) {
     $r->get('/')->to(cb => sub { shift->render('index'); });
 
     $r->get('/meets/:id')->requires(authenticated => 1)->to('Meets#meet');
+    $r->get('/guest/:token')->to('Invites#meet');
 
+    $r->websocket('/ws')->to('Websockets#main');
 }
 
 1;
diff --git a/lib/SeMeet/Controller/Invites.pm b/lib/SeMeet/Controller/Invites.pm
new file mode 100644
index 0000000000000000000000000000000000000000..09fd480b546599ab7d562a131557e0d47a5e198e
--- /dev/null
+++ b/lib/SeMeet/Controller/Invites.pm
@@ -0,0 +1,78 @@
+package SeMeet::Controller::Invites;
+use Mojo::Base 'Mojolicious::Controller', -signatures;
+
+use Data::Random qw(rand_chars);
+
+sub create($c) {
+    $c->openapi->valid_input or return;
+    my $args = $c->req->json;
+
+#### TODO: VALIDATE EMAIL
+
+    my $meet = $c->schema->resultset('Meet')->find({ id => $args->{meet_id} });
+    return $c->error(404, 'NOT_FOUND') if ! $meet;
+
+    my $roles = $meet->user_roles($c->stash->{user}, []);
+    if ( ! ($roles->{moderator} || $roles->{owner}) ) {
+        return $c->error(403, 'ACCESS_DENIED');
+    }
+
+    my $token = rand_chars( set => 'upperalpha', size => 8 );
+
+    my $guard  = $c->schema->txn_scope_guard;
+
+        my $invite = $c->schema->resultset('Invite')->create({
+            token       => $token,
+            user_id     => $c->stash->{user}->id,
+            meet_id     => $meet->id,
+            email       => $args->{email},
+            displayname => $args->{displayname},
+        });
+
+    $guard->commit;
+
+    $invite = $c->schema->resultset('Invite')->find({
+        id => $invite->id
+    });
+
+    $c->trace(\'User %s create invite form meet "%s" with id %d',
+        $c->stash->{user}->username,
+        $meet->name,
+        $invite->id,
+    );
+
+    $c->render(
+        status  => 201,
+        openapi => $c->spec_filter(
+            { $invite->get_columns },
+            'Invite',
+        )
+    );
+}
+
+sub meet($c) {
+
+    my $invite = $c->schema->resultset('Invite')->search({
+        token  => uc($c->stash->{token}),
+        expire => {'>' => \'now()'},
+    })->first;
+
+    if ( ! $invite ) {
+        # TODO: ochrana proti hledani
+        $c->render('invalid_invite');
+        return;
+    }
+
+    my $meet = $c->schema->resultset('Meet')->find({
+        id => $invite->meet_id,
+    });
+
+    return $c->error(404, 'NOT_FOUND') if ! $meet;
+
+    $c->stash->{meet}  = $meet;
+    $c->stash->{token} = $invite->meet_token($meet, $c->config);
+
+    $c->render('meet_guest');
+}
+
+1;
diff --git a/lib/SeMeet/Controller/Meets.pm b/lib/SeMeet/Controller/Meets.pm
index 8325967ce164b086410387f36e06acd76d23390a..4f4670e81909c6a886870439aa76f48bed1cf2b4 100644
--- a/lib/SeMeet/Controller/Meets.pm
+++ b/lib/SeMeet/Controller/Meets.pm
@@ -218,7 +218,6 @@ sub meet($c) { #NENI API!
     my $roles = $meet->user_roles($user, $c->current_user->{groups});
     $c->trace($roles);
 
-
     return $c->error(404, 'NOT_FOUND') if ! $roles->{any};
 
     $c->stash->{meet}  = $meet;
diff --git a/lib/SeMeet/I18N/cs.pm b/lib/SeMeet/I18N/cs.pm
index 5ff7e00f09010bae0570cca058f98bf7fb8c2c65..e4f0d843023f89038759e1e0193ef657739b0da6 100644
--- a/lib/SeMeet/I18N/cs.pm
+++ b/lib/SeMeet/I18N/cs.pm
@@ -14,6 +14,8 @@ our %Lexicon = (
     INPUT_MEET_ADD_GROUPS_PLACEHOLDER   => 'Zadejte název skupiny pro hledání',
     INPUT_MEET_ADD_USERS_PLACEHOLDER    => 'Zadejte jméno osoby pro hledání',
     INPUT_MEET_ADD_GROUPS_LABEL         => 'Přidat skupiny',
+    INPUT_INVITE_DISPLAYNAME_PLACEHOLDER=> 'Jméno a příjmení zvané osoby',
+    INPUT_INVITE_EMAIL_PLACEHOLDER      => 'Mailová adresa',
     ERROR_SERVERSIDE                    => 'Chyba na stranÄ› serveru',
     ERROR_MEET_NAME_REQURED             => "Název nové místnosti je povinný",
     ERROR_MEET_NAME_DUPLICITY           => 'Duplicitní název mistnosti',
@@ -38,6 +40,8 @@ our %Lexicon = (
     'Adding moderators'                 => 'Přidávání moderatorů',
     'Moderators'                        => 'Moderatoři',
     'Invites'                           => 'Pozvánky',
+    'Create invite'                     => 'Vytvořit pozvánku',
+    'Firstname, Lastname'               => '',
 );
 
 1;
diff --git a/lib/SeMeet/Schema/Result/Invite.pm b/lib/SeMeet/Schema/Result/Invite.pm
new file mode 100644
index 0000000000000000000000000000000000000000..3d6254340037872cc7b601036dfdc289742375eb
--- /dev/null
+++ b/lib/SeMeet/Schema/Result/Invite.pm
@@ -0,0 +1,64 @@
+package SeMeet::Schema::Result::Invite;
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+use Mojo::JWT;
+
+use constant MEET_TOKEN_LIFETIME  => 3600 * 24;
+
+our $VERSION = 1;
+
+__PACKAGE__->table('invites');
+
+__PACKAGE__->add_columns(
+    id => {
+        data_type         => 'integer',
+        is_auto_increment => 1,
+        is_nullable       => 0,
+        sequence          => 'uid_seq'
+    },
+    qw(
+        token
+        created
+        expire
+        user_id
+        meet_id
+        email
+        displayname
+    ),
+);
+
+__PACKAGE__->set_primary_key('id');
+
+__PACKAGE__->add_unique_constraint(
+    'token' => [qw(token)]
+);
+
+sub meet_token {
+    my $self = shift;
+    my $meet = shift;
+    my $cfg  = shift;
+
+    return Mojo::JWT->new(
+        secret => $cfg->{jitsi_secret},
+        claims => {
+            aud  => 'semeet',
+            iss  => 'semeet',
+            sub  => 'meet.pirati.cz',
+            room => $meet->uuid,
+            moderator => \0,
+            exp  => time + MEET_TOKEN_LIFETIME,
+            context => {
+                user   => {
+                    name   => $self->displayname . ' (HOST)',
+                    email  => $self->email,
+                }
+            },
+        }
+    )->encode;
+}
+
+1;
+
diff --git a/openapi.yaml b/openapi.yaml
index 21fdd2cef1876a9f41b17ff7a84f3599b6ccee5d..69009f10612650474d4b3f1a115e4e8f86b72ef7 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -81,6 +81,26 @@ components:
           type: string
           nullable: true
           readOnly: true
+    Invite:
+      type: object
+      properties:
+        id:
+          type: integer
+          readOnly: true
+        token:
+          type: string
+          description: Access token
+          readOnly: true
+        expire:
+          type: string
+          description: Cas expirace
+          readOnly: true
+        displayname:
+          type: string
+          description: Jmeno, Prijmeni
+        email:
+          type: string
+          description: E-mail
 
 paths:
   /meets:
@@ -403,3 +423,41 @@ paths:
       responses:
         204:
           description: User deleted
+  /invites:
+    post:
+      x-mojo-to: invites#create
+      security:
+        - Bearer: []
+      tags:
+        - meets
+      summary: "Vytvorit pozvanku"
+      operationId: createInvite
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              type: object
+              properties:
+                displayname:
+                  type: string
+                  description: "Jmeno, Prijmeni"
+                  example: "Jack Sparrow"
+                email:
+                  type: string
+                  description: E-mail
+                  example: "brin@gmail.com"
+                meet_id:
+                  type: integer
+                  example: 666
+              required:
+                - meet_id
+                - displayname
+                - email
+      responses:
+        201:
+          description: Invite created
+          content:
+            application/json:
+              schema:
+               $ref: '#/components/schemas/Invite'
diff --git a/sql/migrations.sql b/sql/migrations.sql
index 665c8f02a3aa56ddf9f5e8916eee5ca3ae8fcd53..8c93ae9168fe92260b0d0ba152bf211c998b00d3 100644
--- a/sql/migrations.sql
+++ b/sql/migrations.sql
@@ -90,3 +90,19 @@ alter table "users" add column "permissions" text;
 
 -- 8 up
 alter table "groups" add column "deleted" timestamp(0);
+
+-- 9 up
+create table "invites" (
+    "id" integer not null default nextval('uid_seq'),
+    "token" varchar(8) not null,
+    "created" timestamp(0) not null default now(),
+    "expire" timestamp(0) not null default now() + '10days',
+    "user_id" integer not null,
+    "meet_id" integer not null,
+    "email" text not null,
+    "displayname" text not null,
+    primary key("id"),
+    unique("token"),
+    foreign key ("meet_id") references "meets" ("id") on update cascade on delete cascade,
+    foreign key ("user_id") references "users" ("id") on update cascade on delete cascade
+);
diff --git a/templates/includes/meet_form.html.ep b/templates/includes/meet_form.html.ep
index d31bd0bb14f78d23c4b6d1077c082fa9e880c2c5..85f4100c1e97d718580accddb5f1955b7177dd67 100644
--- a/templates/includes/meet_form.html.ep
+++ b/templates/includes/meet_form.html.ep
@@ -1,6 +1,6 @@
 <form>
 
-<div class="form-field form-field--error form-field--required mb-4">
+<div class="form-field form-field--required mb-4">
   <label class="form-field__label" for="name"><%=l 'INPUT_MEET_NAME_LABEL' %></label>
   <div class="form-field__wrapper form-field__wrapper--shadowed">
     <input type="text" class="text-input form-field__control form-field--required" value="" v-model="meet.name" id="name" />
diff --git a/templates/includes/meet_invites.html.ep b/templates/includes/meet_invites.html.ep
new file mode 100644
index 0000000000000000000000000000000000000000..7daf8cb3d6644eeb8385c51111071d2c0b6482f7
--- /dev/null
+++ b/templates/includes/meet_invites.html.ep
@@ -0,0 +1,23 @@
+% if ( $roles->{owner}  || $roles->{moderator} ) {
+<div class="grid grid-cols-5">
+
+  <div class="mr-2 col-span-2">
+      <label class="form-field__label"><%=l 'Jméno a příjmení' %></label>
+      <input type="text" class="w-full text-input" value="" v-model.lazy="newInvite.displayname" placeholder="<%=l 'INPUT_INVITE_DISPLAYNAME_PLACEHOLDER' %>"/>
+  </div>
+
+  <div class="mr-2 col-span-3">
+      <label class="form-field__label"><%=l 'E-mail' %></label>
+
+      <div class="flex flex-row">
+          <input type="text" class="w-full text-input mr-2" value="" v-model.lazy="newInvite.email" placeholder="<%=l 'INPUT_INVITE_EMAIL_PLACEHOLDER' %>"/>
+          <button class="btn btn--violet-400 btn--hoveractive text-sm" @click="addInvite()">
+              <div class="btn__body" style="white-space: nowrap;"><%=l 'Create invite' %></div>
+          </button>
+      </div>
+
+  </div>
+
+</div>
+% }
+
diff --git a/templates/invalid_invite.html.ep b/templates/invalid_invite.html.ep
new file mode 100644
index 0000000000000000000000000000000000000000..31b945809d7a0d28b99563575df6764dc91490b8
--- /dev/null
+++ b/templates/invalid_invite.html.ep
@@ -0,0 +1,2 @@
+% layout 'default';
+INVALID INVITE
diff --git a/templates/meet.html.ep b/templates/meet.html.ep
index 7664c68ef6e8199f663f3fa135e63f2034483c71..cc89bcbed7ee0d66e970cc0ee0d47cc5bb46e55f 100644
--- a/templates/meet.html.ep
+++ b/templates/meet.html.ep
@@ -22,8 +22,9 @@
     <div v-if="active_tab == 'moderators'">
 %= include 'includes/meet_moderators'
     </div>
-    <div v-if="active_tab == 'invites'">
-    </div>
+%#    <div v-if="active_tab == 'invites'">
+%#= include 'includes/meet_invites'
+%#    </div>
     <div v-if="active_tab == 'form'">
 % if ( $roles->{owner} ) {
 %= include 'includes/meet_form'
@@ -39,6 +40,7 @@
 <script type="module">
   const GROUPS_URL      = '/api/groups';
   const USERS_URL       = '/api/users';
+  const INVITES_URL     = '/api/invites';
   const MEET_URL        = '/api/meets/<%= stash->{id} %>';
   const ADD_GROUPS_URL  = '/api/meets/<%= stash->{id} %>/groups';
   const ADD_USERS_URL   = '/api/meets/<%= stash->{id} %>/users';
@@ -54,6 +56,8 @@
     data: {
         meet: {},
 
+        newInvite: {},
+
         active_tab: 'groups',
         delete_confirm_visible: false,
 
@@ -219,6 +223,39 @@
             }
           })
         },
+
+        addInvite: function() {
+          if ( this.newInvite.displayname == '' || this.newInvite.email == '' ) {
+            console.log('XXX');
+            return true;
+          }
+
+          this.newInvite.meet_id = <%= stash->{id} %>;
+
+          fetch(INVITES_URL, {
+            method: "POST",
+            headers: API_HEADERS,
+            body: JSON.stringify(this.newInvite),
+          })
+          .then((response) => {
+            if (response.ok) {
+                this.newInvite = {}
+                this.getMeet()
+            }
+          })
+        },
+
+        removeInvite: function(id) {
+          fetch(INVITES_URL + id, {
+            method: "DELETE",
+            headers: API_HEADERS,
+          })
+          .then((response) => {
+            if (response.ok) {
+                this.getMeet()
+            }
+          })
+        },
 % }
     }
 
diff --git a/templates/meet_guest.html.ep b/templates/meet_guest.html.ep
new file mode 100644
index 0000000000000000000000000000000000000000..77d4b055f1e6cb73b14f3ff64de4aac63235d56b
--- /dev/null
+++ b/templates/meet_guest.html.ep
@@ -0,0 +1,2 @@
+% layout 'default';
+%= include 'includes/meet'