Tymeslot

tymeslot.app
Tymeslot

Open-source (AGPL-3.0), self-hostable scheduling and calendar platform. Guests book without creating an account; hosts get a full calendar with two-way sync to Google, Outlook and iCloud. A managed cloud plan is offered too, not self-hosted.

Open Source

Tymeslot Source Code

Author

Tymeslot

Description

Open-source meeting scheduling platform built with Elixir/Phoenix LiveView. Self-host or use the managed cloud at tymeslot.app.

#appointment#appointment-booking#appointment-scheduling#appointments#calendar#calendars#calendly-alternative#docker#elixir#phoenix-liveview#postgres#scheduling

Homepage

https://tymeslot.app

License

AGPL-3.0

Created

07 Aug 25

Last Updated

20 Jul 26

Latest version

v1.4.4

Primary Language

Elixir

Size

175,493 KB

Stars

144

Forks

16

Watchers

144

Language Usage

Language Usage

Star History

Star History

Top Contributors

Recent Commits

  • Luka Breitig (17 Jul 26)

    chore(core): backfill 1.4.2 changelog and order the version catalog The 1.4.2 entry shipped with an empty changelog, which stops Cloudron surfacing the version as an available update. Give it a maintenance changelog so the entry is non-blank. Also sort the catalog entries ascending by semver (0.100.x now correctly follows 0.99.x) so the file is readable and future additions append at the end with minimal diffs. Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (15 Jul 26)

    fix(core): show the saved icon on meeting type cards The meeting type list card rendered @type.icon as a bare <span class={[@type.icon, ...]}>, the same removed .hero-* mask pattern. A meeting type with a chosen icon showed a blank gap on its card. Render it through Icons.icon like the rest of the file. Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (15 Jul 26)

    fix(core): show the meeting type icon picker and mode-toggle icons The icon picker rendered each option as a bare <span class="hero-*">, and the In-Person / Video mode toggle used the same pattern. Both relied on the Tailwind .hero-* mask utility, which no longer exists since heroicons render as inline SVG via the <.icon> component. The tiles painted blank. Route all three through Icons.icon instead. Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (17 Jul 26)

    chore: bump version to 1.4.4 Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (17 Jul 26)

    fix(core): prevent availability crash on days the attendee's clocks change When an owner's hours reach across the attendee's midnight, slot generation clamps the range to the attendee's day boundaries. Those boundaries were built with DateTime.new!/3, which raises both when a wall-clock time does not exist (spring forward) and when it occurs twice (fall back). Attendees in Santiago, Havana, Cairo, Beirut and the Azores therefore saw the booking page fail on their DST changeover days, whenever the owner sat far enough east or west to push availability over the boundary. Resolve the boundaries through resolve_wall_time/3, which the module already used for break times: a gap snaps forward to the first instant that exists, and an ambiguous time anchors to the first occurrence. Verified across every supported timezone for 2026 and 2027: 118,990 boundary constructions, no failures. Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (17 Jul 26)

    docs(core): document the Connect webhook endpoint details in the README The Stripe Connect setup section told operators to create a webhook endpoint but not where to point it, which events to subscribe, or which API version to select. Spell out the /webhooks/stripe/connect URL, the six required events, the connected-accounts listener mode, and the pinned API version, and note that upgrades never rotate webhook signing secrets. Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (17 Jul 26)

    chore: correct the hackney override rationale in the mix files The override comments justified forcing hackney 4.x by analysing caldav_client and tzdata, neither of which uses hackney at runtime, and tzdata is not even in the dependency tree (timezone data comes from tz). The real 1.x constraints are caldav_client's optional requirement and httpoison's, pulled in by Wallaby for tests only. More importantly, the libraries that do run on hackney 4.x in production are Swoosh's Postmark adapter and stripity_stripe, and neither path is exercised by CI, so they need staging verification after the upgrade. Rewrite both comments to state this accurately. Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (17 Jul 26)

    fix(core): allow the Stripe Connect onboarding redirect through the CSP The Connect Stripe button posts to the onboarding controller, which redirects to connect.stripe.com. Chrome enforces the form-action CSP directive on redirects of a form submission, and the policy only listed billing.stripe.com and checkout.stripe.com, so the browser blocked the redirect and onboarding never left the dashboard. Add connect.stripe.com to form-action. Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (17 Jul 26)

    chore: upgrade stripity_stripe to 3.3.2 on hackney 4 Raise stripity_stripe from 3.2.0 to 3.3.2 and move the Stripe API pin from 2022-11-15 to 2025-11-17.clover, the version the new release generates its structs from. The consumers of the fields Stripe moved in 2025-03-31.basil were prepared in the two preceding commits and accept both shapes. stripity_stripe 3.3 requires hackney 4.x, which caldav_client and tzdata cannot accept, so hackney 4.6.0 is forced via override in both the umbrella root and Core mix.exs (umbrella resolution only honours root overrides; the Core entry covers standalone resolution). This is safe because stripity_stripe is the only production consumer of hackney: CalDAV sync is a Req implementation, SaaS production mail goes through Finch, and tzdata is not installed. hackney keeps the 1.x request API, and a live HTTPS request against api.stripe.com was verified on 4.6.0. Wallaby end-to-end runs pull httpoison, which declares hackney 1.x; the override forces it onto 4.6.0 there too, so watch the first e2e run. Dependency changes across both lockfiles, kept aligned for the umbrella and standalone deployments: stripity_stripe 3.2.0 -> 3.3.2 hackney 1.25.0 -> 4.6.0 certifi 2.15.0 -> 2.17.0 idna 6.1.1 -> 7.1.0 parse_trans 3.4.1 -> 3.4.2 h2 0.10.4, quic 1.7.1, webtransport 0.4.3 (new, hackney 4) metrics, tzdata, unicode_util_compat (removed, no longer referenced) The webhook payload shape follows the endpoint version configured in the Stripe dashboard, not this pin: raise the webhook endpoints to 2025-11-17.clover when deploying. Also note stripity_stripe 3.3 changes [:stripe, :request] telemetry metadata keys and returns unknown Stripe objects with string keys from its converter; nothing in this codebase consumes either. Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (17 Jul 26)

    fix(core): route invoice and dispute webhooks without legacy charge links Stripe API 2025-03-31.basil restructured two references these handlers route on. The invoice's subscription id moved from the top-level field into parent.subscription_details, so renewal and failure processing would silently take the no-subscription path. The charge lost its invoice and subscription fields, so every dispute would be treated as a one-off payment. Invoice handling now reads the subscription reference from either location, unwrapping an expanded subscription object where present. Dispute routing keeps honouring the legacy charge fields and otherwise falls back to our own records: one-off charges always leave a local transaction for their customer, so a customer without one can only mean a subscription charge. Without a configured subscription manager there is nothing to forward to, and disputes stay on the local alert path as before. The subscriptions context suite mutated the global subscription manager configuration while running async and without restoring it, leaking into any suite that reads the setting at runtime. It now runs synchronously and restores the original value after every test, and the dispute suite pins its own configuration. Changelog: skip Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (17 Jul 26)

    chore(core): pin the Stripe API version explicitly The Stripe wire contract was inherited from stripity_stripe's built-in default rather than declared here, so it moved with the dependency instead of with our code. Version 3.3 defaults to 2025-11-17.clover, which relocates current_period_start/end off Subscription onto its items โ€” fields the subscription persistence and payment event paths read. Upgrading the library would have changed the contract with no diff in this repo to show for it. Pinning to 2022-11-15 matches the effective version today, so this is a no-op for behaviour. It makes the API version an explicit decision, and a dependency upgrade now fails loudly at the code that reads the changed fields instead of shifting underneath it. Set in config.exs rather than in the runtime.exs blocks, which are conditional on environment and on STRIPE_SECRET_KEY being present: the version has to hold whether or not a key is configured. Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (17 Jul 26)

    chore: update dependencies with security and bug fixes Bump 13 locked dependencies to their latest compatible releases. Six carry security fixes: phoenix 1.8.8 -> 1.8.9 CVE-2026-56811, CVE-2026-56812 phoenix_live_view 1.2.4 -> 1.2.7 CVE-2026-58228 mint 1.9.1 -> 1.9.3 CVE-2026-59249, CVE-2026-58229, CVE-2026-59246 plug 1.20.2 -> 1.20.3 CVE-2026-56814, CVE-2026-56813 postgrex 0.22.2 -> 0.22.3 CVE-2026-58225 swoosh 1.26.2 -> 1.26.3 CVE-2026-54893 The rest are bug fixes and housekeeping: castore 1.0.19 -> 1.0.20 ecto 3.14.0 -> 3.14.1 ex_machina 2.8.0 -> 2.8.1 mdex_native 0.2.4 -> 0.2.5 req 0.6.2 -> 0.6.3 stream_data 1.3.0 -> 1.4.0 websock_adapter 0.5.9 -> 0.6.0 Notes on the less obvious ones: websock_adapter follows phoenix relaxing its requirement from ~> 0.5.3 to ~> 0.5. It defaults max_frame_size to 10MB where it was previously unset; under Bandit that raises the effective limit from its 8MB default, and neither socket sets the option. castore drops the Entrust and SecureSign Root CA12 roots. Both are already distrusted upstream. plug now counts file bytes towards Plug.Parsers' :length limit. Uploads go through LiveView's channel-based allow_upload and bypass Plug.Parsers entirely, so the 5MB endpoint limit is unaffected. stripity_stripe and httpoison are held back deliberately. Both now require hackney ~> 4.0, which cannot resolve against caldav_client and tzdata, each still pinned to hackney 1.x. stripity_stripe 3.3 also moves its pinned Stripe API version to 2025-11-17.clover, which removes current_period_start/end from subscriptions. Both lockfiles move together to satisfy the umbrella/standalone alignment check. Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (17 Jul 26)

    feat(core): show the meeting description on meeting type cards The card in meeting settings listed only the name, duration, location and calendar. Where a meeting type has a description, it now appears between the title and that detail row, clamped to two lines. Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (17 Jul 26)

    chore: bump version to 1.4.3 Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (17 Jul 26)

    fix(core): clear the reschedule-requested state once the attendee rebooks Requesting a reschedule used to overwrite the meeting status with "reschedule_requested", destroying the underlying lifecycle status (pending, awaiting_payment, confirmed) with nothing restoring it once the attendee rebooked. Rebooked meetings stayed stuck: wrong dashboard badge, excluded from reminders and upcoming lists. The request is now tracked in a dedicated reschedule_requested_at timestamp, owned by the new Meetings.MeetingState module together with the derived predicates and their query-side mirrors, and rebooking clears it while the lifecycle status survives untouched. Existing reschedule_requested rows are migrated onto the new field. The flow is hardened along the way: a duplicate reschedule request is rejected instead of re-emailing the attendee, the slot update and the request email are enqueued atomically, the .ics export and external calendar sync ignore voided slots, a stale calendar deletion job can no longer strip a rebooked meeting's event, and reminder sent-tracking resets on reschedule so reminders fire for the new time. Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (16 Jul 26)

    fix(core): delete pending reminder jobs when a meeting is cancelled Cancellation left reminder jobs queued and relied on the reminder worker discarding them at send time via the status check. Delete them at cancellation instead, in both the user-initiated and the externally-triggered flow, mirroring the reschedule-request cleanup. No user-visible change: reminders for cancelled meetings were already suppressed when the job ran. Changelog: skip Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (16 Jul 26)

    fix(core): remove calendar event and reminders when a reschedule is requested Sending a reschedule request tells the attendee their appointment has been cancelled, but the system kept behaving as if the booking still stood: the event stayed in the organizer's calendar and reminder emails for the void time slot were still delivered to both parties. Requesting a reschedule now voids the original slot properly: - The calendar event is deleted, matching the cancellation flow. - Pending reminder email jobs are dropped, and the reminder worker discards any job already in flight for a reschedule_requested meeting. - When the attendee books a new time, the meeting returns to "confirmed" - the dashboard badge clears and conflict checks count the meeting at its new time again. The calendar event is recreated through the existing update-404 recovery path. - Reminders are re-pinned to the new meeting time on every reschedule. Previously they stayed aimed at the original slot because the reminder-job cleanup compared Oban worker names against the "Elixir."-prefixed module string and never matched; the lookup is now normalised at the query boundary. Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (17 Jul 26)

    fix(core): show current city names for legacy timezone ids browsers report Browsers running on older ICU/CLDR data report pre-rename IANA ids such as Asia/Katmandu or Asia/Calcutta from Intl.DateTimeFormat().resolvedOptions().timeZone. Since these resolve in the time zone database, times already rendered correctly, but the timezone picker fell back to the outdated spelling with a globe icon instead of the curated City, Country label and flag. The legacy-id normalisation map now covers all genuine IANA renames (Kolkata, Kathmandu, Yangon, Ho Chi Minh City, Nuuk and others) alongside the existing Kiev to Kyiv entry, so visitors on older browsers see the modern city name. Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (16 Jul 26)

    fix(core): show cancel and reschedule times in the attendee's timezone The cancel and reschedule pages formatted the meeting's stored UTC start time directly, so the clock rendered in UTC while being labelled with the attendee's zone, asserting a time that was not that time. The shared meeting details component now shifts the stored instant into the attendee's zone before formatting, falling back to the organiser's zone when the attendee's is unknown, and labels the clock with the zone it actually rendered in. Journey tests now seed a non-UTC attendee zone so the shift is proven through real theme and dispatcher wiring, not only in isolation. Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (16 Jul 26)

    fix(core): accept valid timezones the format check wrongly rejected Timezone validation previously used format regexes that rejected real zones the picker itself offers, such as America/Argentina/Buenos_Aires and Etc/GMT+5, silently discarding the visitor's choice. Validation now asks the IANA time zone database directly, through a single context entry point shared by the security sanitiser and the profile timezone form. Validity ("may this zone be stored?") is now distinct from the curated picker list ("can it be shown as City, Country with a flag?"), exposed as valid?/1 and offered?/1. The curated list also drops two ids that were never real IANA zones (Asia/Hanoi, America/Calgary), keeping both cities as search aliases of their canonical zones, and corrects Port Louis to Indian/Mauritius. Rejected values now report "Unknown timezone" rather than a misleading format error. Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (16 Jul 26)

    style(core): use hyphens instead of em dashes in translatable strings Keeps punctuation consistent with the rest of the copy and matches the existing hyphenated style already used elsewhere (e.g. the appointment confirmation subject line). Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (14 Jul 26)

    test(core): realign the e2e suite with three shipped behaviour changes Five e2e features were asserting on behaviour the product has since moved past. None of them were catching a regression. Dashboard tour: the overlay fades out over 700ms via phx-remove before LiveView drops it. Wallaby's refute_has is not a waiter โ€” it retries only until an element appears โ€” so it fired mid-fade. Wait for actual removal with a count: 0 query. The mobile case was passing for the wrong reason (the overlay is display: none below 1024px, so an invisibility check passes even if the hook never runs); it now requires real removal too. Embed size matrix: back-step is gated on entered_via_overview, so a direct booking link deliberately renders no Back button. The full-height matrix now walks the real overview to schedule journey, which restores Back and covers the wider two-button actions row. The short-viewport features keep deep-linking โ€” below roughly 250px of usable height the overview's own CTA sits past the end of a page that doesn't scroll โ€” and assert on the actions row instead, via a new schedule-actions test hook on both themes. Both buttons share the row's vertical extent, so the pinning and no-overlap contracts are unchanged. Account settings: changing a password now revokes every session and redirects to login, so the old "Password updated" flash no longer exists. The test follows the sign-out through and signs back in with the new password. It does not assert the flash wording: the revocation's disconnect tears the socket down before the LiveView's own redirect flash reaches the browser, so which notice wins is a race. Changelog: skip Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (14 Jul 26)

    fix(core): retry the initial Outlook calendar sync after connecting The first delta snapshot was seeded from a fire-and-forget supervised task spawned by the OAuth callback, so a transient Graph failure left the calendar unsynced until the next fallback sweep, with no retry. Enqueue RefreshOutlookCalendarWorker instead. It already owns this path โ€” a nil graph_delta_link makes it bootstrap the baseline and register the Graph subscription โ€” and Oban gives the seed retries. This mirrors what the Google OAuth flow has been doing all along. Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (14 Jul 26)

    fix(core): handle unexpected Graph failures without crashing Outlook sync The circuit breaker hands back {:error, reason} for every failure it sees, but GraphSubscription matched only :circuit_open. A rescued exception, a missing breaker, or hitting the 50-page delta pagination limit therefore raised CaseClauseError instead of returning an error tuple, killing the caller rather than failing the sync cleanly. Match the breaker's error shape as a catch-all in both Graph calls, and widen the bootstrap_sync/register specs (and the behaviour callbacks) to match what they can actually return. Changelog: skip Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (14 Jul 26)

    chore: bump version to 1.4.2 Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (14 Jul 26)

    chore(core): run the DCO check without maintainer approval on fork PRs The check ran on pull_request, so GitHub held it for approval whenever a first-time contributor opened a pull request from a fork. Because dco-check is a required status check, the pull request then sat blocked on a check that never reported, rather than failing honestly with the sign-off instructions. pull_request_target runs the workflow from the base branch in this repository's context, so it needs no approval. That is only safe because the fork's code is never touched: the checkout is gone, and the commit metadata is read from the API with a read-only token. Signed-off-by: Luka Breitig <[email protected]>

  • Luka Breitig (08 Jul 26)

    chore: bump version to 1.4.1

  • Luka Breitig (08 Jul 26)

    fix(core): honour Changelog: skip in the Cloudron changelog config Same spaceless-footer bug as the umbrella cliff.toml: git-cliff reconstructs the footer without a space after the separator, so "^Changelog: skip" never matched and skipped commits still reached the Cloudron changelog. Match "^Changelog:\s*skip". Changelog: skip

  • Luka Breitig (08 Jul 26)

    docs(core): add CONTRIBUTORS and link it from the README Credit code, translation, idea, and bug-report contributors in a dedicated CONTRIBUTORS.md โ€” the Gitea-to-GitHub mirror means the GitHub contributors graph is incomplete. Link it from the README with a nod to @dani for OAuth, French localisation and CalDAV fixes.

  • Luka Breitig (08 Jul 26)

    test(core): update error page assertion to redesigned 404 copy

Tymeslot Website

Website

Open-Source Meeting Scheduling Software ยท Tymeslot

Eliminate scheduling back-and-forth with Tymeslot. Share your availability, let others book instantly, and sync with your calendar and video tools.

Redirects

Does not redirect

Security Checks

2 security checks failed (63 passed)

  • Domain Recently Created
  • Domain Very Recently Created

Server Details

  • IP Address 94.16.119.60
  • Hostname v2202503260852324142.megasrv.de
  • Location Nuremberg, Bayern, Germany, EU
  • ISP Anexia Cloud Solutions GmbH
  • ASN AS197540

Associated Countries

  • US US
  • DE DE

Safety Score

Website marked as safe

100%

Blacklist Check

tymeslot.app was found on 0 blacklists

  • AntiSocial Blacklist
  • Artists Against 419
  • Badbitcoin
  • Bambenek Consulting
  • CERT Polska
  • CoinBlockerLists
  • CRDF
  • CryptoScamDB
  • EtherAddressLookup
  • EtherScamDB
  • Fake Website Buster
  • MetaMask EthPhishing
  • NABP Not Recommended Sites
  • OpenPhish
  • PetScams
  • PhishFeed
  • PhishFort
  • Phishing.Database
  • PhishStats
  • PhishTank
  • Phishunt
  • RPiList Not Serious
  • Scam.Directory
  • SecureReload Phishing List
  • Spam404
  • StopGunScams
  • Suspicious Hosting IP
  • ThreatFox
  • ThreatLog
  • TweetFeed
  • URLhaus
  • ViriBack C2 Tracker

Website Preview

Website preview

Tymeslot Reviews

More Scheduling

  • Open-source & self-hostable scheduling tool that helps you find the best date and time to meet. Create a poll with a few options, share the link, and let your participants vote on when they're available. No more back-and-forth emails.

About the Data: Tymeslot

Change History

API

You can access Tymeslot's data programmatically via our API. Simply make a GET request to:

https://api.awesome-privacy.xyz/v1/services/tymeslot

The REST API is free, no-auth and CORS-enabled. To learn more, view the API Docs or read the API Usage Guide.

Share Tymeslot

Help your friends compare Scheduling, and pick privacy-respecting software and services.
Share Tymeslot and Awesome Privacy with your network!

View Scheduling (2)