Common Setup

Mail, storage, cron, queues, Firebase and APNs — the parts the website and the app both depend on.
Version: 1.7.0
Stack: Laravel 13 · Flutter 3 · MySQL 8

Item: Learnify LMS

Mail (SMTP)

Email is not optional — registration, password resets, order receipts, enrolment welcomes, certificate notices, assignment and live-class updates, payout and refund decisions and campaign sends all go through it. Configure it under Settings → Mail & Storage, or in .env if you prefer.

SettingTypical value
Hostsmtp.yourprovider.com
Port587 (TLS) or 465 (SSL)
Encryptiontls
Username / passwordThe mailbox credentials from your provider
From addressAn address on your own domain, with SPF and DKIM set

Sending from a Gmail or Yahoo address on a different domain is the most common reason mail lands in spam. Use an address on the domain the site runs on.

There is a send test email button on the same page — use it before you go further.

Mail & storage settings
Mail & storage settings

File Storage

Uploads — course thumbnails, lesson videos, resources, certificates, avatars — go to the local disk by default. That is fine to start with. For anything at scale, switch to S3 or an S3-compatible provider on the same settings page.

On a fresh manual install (not through the wizard) create the public symlink once:

php artisan storage:link

You can also cap how much a single account may upload, under the same settings group.

Cron & Scheduler

Everything scheduled — drip lesson release, email campaigns, live-class reminders, payout processing, certificate issuing, backups, cleanup and (since 1.7.0) the 10-minute re-check of payments awaiting gateway confirmation — runs without a cron job since 1.6.0: due tasks run while people use the site (a missed one runs on the next visit). For exact timing on a quiet site, add one cron entry; it takes over automatically:

* * * * * cd /path/to/your/site && php artisan schedule:run >> /dev/null 2>&1

On shared hosting, cPanel's Cron Jobs screen accepts the same line. Use the full path to PHP 8.3 if the default php is older.

Live exams. The scheduler also runs quizzes:publish-results. A quiz with a Publish results at time keeps scores hidden (“Answers submitted”) until that command releases them; without cron this happens on the first visit after the publish time.

If your host can only open a web address on a timer (cPanel wget, cron-job.org), use the secret cron address shown on System → Health. That page shows whether the scheduler runs on site visits or from your cron, and when it last ran.

Queue Worker

Slow work — campaign sends, large report exports, webhook delivery — is queued on the database driver the installer sets. Since 1.6.0 no worker is required: waiting jobs are processed for a few seconds after page visits. For a busy site or large campaigns, run a worker under Supervisor; once it is running the site stops processing jobs on visits by itself:

[program:learnify-worker]
command=php /path/to/your/site/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/path/to/your/site/storage/logs/worker.log

Firebase Project

Push notifications — browser and app — go through Firebase Cloud Messaging. Skip this section entirely if you do not want push.

  1. Create the project

    At console.firebase.google.com, create a project. Analytics is optional.

  2. Add the Android app

    Use the same application id as the Flutter project. Download google-services.json.

  3. Add the iOS app

    Use the same bundle id. Download GoogleService-Info.plist.

  4. Create a service account

    Project settings → Service accounts → Generate new private key. That JSON goes into the admin panel.

  5. Paste it into Learnify

    Settings → Integrations → Firebase. Paste the service account JSON and switch push on.

Integrations
Integrations
Push composer
Push composer

Android

Drop google-services.json into the Flutter project at:

AppSourceCode/android/app/google-services.json

Then rebuild. If the file is missing the app still runs — it just will not receive push.

iOS & APNs

iOS needs one extra step that Android does not: Apple has to authorise Firebase to send to your app.

  1. Create an APNs key

    In the Apple Developer portal: Certificates, Identifiers & Profiles → Keys. Enable Apple Push Notifications service and download the .p8. You can only download it once.

  2. Upload it to Firebase

    Project settings → Cloud Messaging → APNs Authentication Key. You will need the Key ID and your Team ID.

  3. Add the plist

    Put GoogleService-Info.plist in AppSourceCode/ios/Runner/ and add it to the Runner target in Xcode.

  4. Turn on the capability

    In Xcode, under Signing & Capabilities, add Push Notifications and Background Modes → Remote notifications.

Push does not work on the iOS Simulator. Test it on a real device.

Testing a Push

Sign in on a device, then send from Marketing → Push in the admin panel. If nothing arrives, check in this order:

  • The device registered a token — it appears under the user in the admin panel.
  • Push is enabled in Settings → Integrations and the service account JSON is valid.
  • The app was granted notification permission on the device.
  • On iOS: the APNs key is uploaded to Firebase and the build is a real device build, not the Simulator.

Video Sources

A lesson can take its video from any of these, chosen per lesson — you are not locked into one:

SourceWhen to use it
YouTube / VimeoFastest to start; the platform handles delivery. Unlisted videos work.
Google DrivePaste the share link (set to “Anyone with the link”); the Drive player is embedded. Handy for small teams; not protected.
Direct URLAn MP4 you host anywhere reachable over HTTPS.
HLS (.m3u8)Adaptive streaming from your own encoder or CDN.
Uploaded fileStored on your disk and served through a signed, range-aware endpoint so the URL cannot simply be copied.
Bunny Stream / Cloudflare StreamManaged video hosting with signed playback. Add the keys under Integrations.

Uploaded and managed-provider videos are the only ones that are genuinely protected. A YouTube or plain-URL video can always be opened directly by someone who wants to.

Subtitles can be attached per lesson as WebVTT files, and SCORM 1.2 / 2004 packages can be uploaded as a lesson type of their own.

Upload Limits

If large lesson uploads fail, it is almost always PHP rather than the application. Raise these in php.ini and restart PHP:

upload_max_filesize = 512M
post_max_size = 512M
max_execution_time = 600
memory_limit = 512M

Nginx also needs client_max_body_size 512M; in the server block.