Compare commits
5 Commits
996ae20bbb
...
e7895cb70c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7895cb70c | ||
|
|
22e2b2457a | ||
|
|
011ca2a408 | ||
|
|
e73342f1fb | ||
|
|
41c1e7ad54 |
@@ -21,6 +21,10 @@ class Mailbox extends Component
|
||||
|
||||
public $search = '';
|
||||
|
||||
public $viewMode = 'text'; // text | html
|
||||
|
||||
public $allowRemoteContent = false;
|
||||
|
||||
public $page = 1;
|
||||
|
||||
public $totalPages = 5;
|
||||
@@ -38,16 +42,17 @@ class Mailbox extends Component
|
||||
|
||||
public function getEmailsProperty()
|
||||
{
|
||||
// Mock emails based on mailbox ID for demonstration
|
||||
$emails = [
|
||||
1 => [
|
||||
// Mock emails based on mailbox ID
|
||||
$baseEmails = [
|
||||
1 => [ // Inbox
|
||||
[
|
||||
'id' => 1,
|
||||
'from_name' => 'GitHub Security',
|
||||
'from_email' => 'noreply@github.com',
|
||||
'subject' => '[GitHub] A new personal access token was created',
|
||||
'preview' => 'A new personal access token (classic) was recently added to your account.',
|
||||
'content' => '<p>Hi @idevakk,</p><p>A new personal access token (classic) was recently added to your account IDEVAKK.</p><p>If this was you, you can safely ignore this email.</p><p>If this was not you, please visit https://github.com/settings/tokens to revoke the token.</p>',
|
||||
'body_html' => '<p>Hi @idevakk,</p><p>A new personal access token (classic) was recently added to your account IDEVAKK.</p><p>If this was you, you can safely ignore this email.</p><p>If this was not you, please visit https://github.com/settings/tokens to revoke the token.</p>',
|
||||
'body_text' => "Hi @idevakk,\n\nA new personal access token (classic) was recently added to your account IDEVAKK.\n\nIf this was you, you can safely ignore this email.\n\nIf this was not you, please visit https://github.com/settings/tokens to revoke the token.",
|
||||
'time' => '10:24 AM',
|
||||
'unread' => true,
|
||||
'flagged' => true,
|
||||
@@ -59,112 +64,131 @@ class Mailbox extends Component
|
||||
'from_email' => 'updates@linear.app',
|
||||
'subject' => 'New issue assigned: [UI-124] Fix sidebar overflow',
|
||||
'preview' => 'You have been assigned to a new issue in the UI project. Please review the details...',
|
||||
'content' => '<p>Hello,</p><p>You have been assigned to <strong>[UI-124] Fix sidebar overflow in mobile view</strong>.</p><p>Priority: High</p><p>Project: Imail Revamp</p><p>View details at https://linear.app/imail/issue/UI-124</p>',
|
||||
'body_html' => '<p>Hello,</p><p>You have been assigned to <strong>[UI-124] Fix sidebar overflow in mobile view</strong>.</p><p>Priority: High</p><p>Project: Imail Revamp</p><p>View details at https://linear.app/imail/issue/UI-124</p>',
|
||||
'body_text' => "Hello,\n\nYou have been assigned to [UI-124] Fix sidebar overflow in mobile view.\n\nPriority: High\nProject: Imail Revamp\n\nView details at https://linear.app/imail/issue/UI-124",
|
||||
'time' => '11:45 AM',
|
||||
'unread' => true,
|
||||
'flagged' => false,
|
||||
'attachments' => [],
|
||||
],
|
||||
[
|
||||
'id' => 102,
|
||||
'from_name' => 'Canva',
|
||||
'from_email' => 'design@canva.com',
|
||||
'subject' => 'Your design "Imail Presentation" is ready',
|
||||
'preview' => 'Collaborate with your team on your latest design for the Imail product launch.',
|
||||
'content' => '<p>Hey there!</p><p>Your team is waiting for your feedback on the <strong>Imail Presentation</strong> design.</p><p>Check the latest comments and approve the final version.</p>',
|
||||
'time' => '9:12 AM',
|
||||
'unread' => false,
|
||||
'id' => 104,
|
||||
'from_name' => 'Unsplash Updates',
|
||||
'from_email' => 'hello@unsplash.com',
|
||||
'subject' => 'Featured Photos: Cinematic Landscapes',
|
||||
'preview' => 'Check out this week\'s curated collection of cinematic landscape photography...',
|
||||
'body_html' => '<p>Hello Zemailer,</p><p>We have curated some new cinematic landscapes for your next project:</p><div style="margin: 20px 0;"><img src="https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=800&q=80" style="width: 100%; border-radius: 12px; margin-bottom: 15px;" alt="Mountain Landscape"><img src="https://images.unsplash.com/photo-1470770841072-f978cf4d019e?auto=format&fit=crop&w=800&q=80" style="width: 100%; border-radius: 12px;" alt="Lake Landscape"></div><p>Feel free to use them in your designs!</p>',
|
||||
'body_text' => "Hello Zemailer,\n\nWe have curated some new cinematic landscapes for your next project.\n\n[Images are blocked by default in privacy mode]\n\nCheck them out on Unsplash!",
|
||||
'time' => '7:45 AM',
|
||||
'unread' => true,
|
||||
'flagged' => false,
|
||||
'attachments' => [
|
||||
['name' => 'presentation_v1.pdf', 'size' => '4.2 MB'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 103,
|
||||
'from_name' => 'Figma',
|
||||
'from_email' => 'notifications@figma.com',
|
||||
'subject' => 'Atul Kumar mentioned you in "Mobile App (Draft)"',
|
||||
'preview' => '"@idevakk take a look at the revised QR modal design, let me know if..."',
|
||||
'content' => '<p><strong>Atul Kumar</strong> mentioned you in a comment on <strong>Mobile App (Draft)</strong>:</p><blockquote>"@idevakk take a look at the revised QR modal design, let me know if the proportions look right to you."</blockquote><p>Reply in Figma or view the comment online.</p>',
|
||||
'time' => '8:30 AM',
|
||||
'unread' => false,
|
||||
'flagged' => true,
|
||||
'attachments' => [],
|
||||
],
|
||||
// Generated Inbox items to reach 15
|
||||
],
|
||||
2 => [
|
||||
2 => [ // Sent
|
||||
[
|
||||
'id' => 2,
|
||||
'from_name' => 'Stripe',
|
||||
'from_email' => 'support@stripe.com',
|
||||
'subject' => 'Your weekly payment report',
|
||||
'preview' => 'Your weekly report for the period of Feb 24 - Mar 2 is now available.',
|
||||
'content' => '<p>Hello,</p><p>Your weekly report for the period of Feb 24 - Mar 2 is now available in your dashboard.</p><p>Total Volume: $12,450.00</p><p>View the full report details online.</p>',
|
||||
'body_html' => '<p>Hello,</p><p>Your weekly report for the period of Feb 24 - Mar 2 is now available in your dashboard.</p><p>Total Volume: $12,450.00</p><p>View the full report details online.</p>',
|
||||
'body_text' => "Hello,\n\nYour weekly report for the period of Feb 24 - Mar 2 is now available in your dashboard.\n\nTotal Volume: $12,450.00\n\nView the full report details online.",
|
||||
'time' => 'Yesterday',
|
||||
'unread' => false,
|
||||
'flagged' => false,
|
||||
'attachments' => [
|
||||
['name' => 'report_mar_02.pdf', 'size' => '1.2 MB'],
|
||||
'attachments' => [['name' => 'report_mar_02.pdf', 'size' => '1.2 MB']],
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 201,
|
||||
'from_name' => 'Postmark',
|
||||
'from_email' => 'alerts@postmarkapp.com',
|
||||
'subject' => 'Outbound volume spike detected',
|
||||
'preview' => 'We noticed a sudden increase in outbound emails from your "Production" server.',
|
||||
'content' => '<p>Alert: Outbound volume spike.</p><p>Server: Production</p><p>We detected 5,000+ emails sent in the last hour. Please ensure this is expected activity.</p>',
|
||||
'time' => 'Yesterday',
|
||||
'unread' => true,
|
||||
'flagged' => false,
|
||||
'attachments' => [],
|
||||
],
|
||||
[
|
||||
'id' => 202,
|
||||
'from_name' => 'Vercel',
|
||||
'from_email' => 'deployments@vercel.com',
|
||||
'subject' => 'Team "idevakk" deployment successful',
|
||||
'preview' => 'Production deployment for the imail-frontend project has completed.',
|
||||
'content' => '<p>Your deployment is live!</p><p>Project: imail-frontend</p><p>Command: <code>npm run build</code></p><p>View your deployment here: https://imail.app</p>',
|
||||
'time' => 'Mar 2',
|
||||
'unread' => false,
|
||||
'flagged' => false,
|
||||
'attachments' => [],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
return $emails[$this->currentMailboxId] ?? [
|
||||
3 => [ // Notifications
|
||||
[
|
||||
'id' => 3,
|
||||
'from_name' => 'Slack',
|
||||
'from_email' => 'notifications@slack.com',
|
||||
'subject' => 'You have 12 unread messages from your team',
|
||||
'preview' => 'Atul Kumar: "Did you check the new API endpoints? We need them for..."',
|
||||
'content' => '<p>You have new activity in Slack.</p><ul><li><strong>#dev-chat</strong>: 8 new messages</li><li><strong>#announcements</strong>: 4 new messages</li></ul>',
|
||||
'body_html' => '<p>You have new activity in Slack.</p><ul><li><strong>#dev-chat</strong>: 8 new messages</li><li><strong>#announcements</strong>: 4 new messages</li></ul>',
|
||||
'body_text' => "You have new activity in Slack.\n\n#dev-chat: 8 new messages\n#announcements: 4 new messages",
|
||||
'time' => 'Mar 1',
|
||||
'unread' => true,
|
||||
'flagged' => false,
|
||||
'attachments' => [],
|
||||
],
|
||||
[
|
||||
'id' => 301,
|
||||
'from_name' => 'Zoom',
|
||||
'from_email' => 'no-reply@zoom.us',
|
||||
'subject' => 'Meeting Reminder: "Sprint Planning"',
|
||||
'preview' => 'Your Sprint Planning meeting is scheduled to start in 15 minutes.',
|
||||
'content' => '<p>Friendly reminder that your Sprint Planning call starts soon.</p><p>Link: https://zoom.us/j/123456789</p>',
|
||||
'time' => 'Feb 28',
|
||||
'unread' => false,
|
||||
'flagged' => false,
|
||||
'attachments' => [],
|
||||
],
|
||||
];
|
||||
|
||||
// Fill Inbox (MB 1) to 15
|
||||
for ($i = 5; $i <= 18; $i++) {
|
||||
$baseEmails[1][] = [
|
||||
'id' => 1000 + $i,
|
||||
'from_name' => "Partner $i",
|
||||
'from_email' => "partner-$i@example.com",
|
||||
'subject' => "Follow-up proposal #$i",
|
||||
'preview' => "I wanted to check in regarding our previous discussion on project $i...",
|
||||
'body_html' => "<p>Hello,</p><p>This is a follow-up email #$i regarding our partnership.</p>",
|
||||
'body_text' => "Hello,\n\nThis is a follow-up email #$i regarding our partnership.",
|
||||
'time' => 'Mar 1',
|
||||
'unread' => $i % 3 === 0,
|
||||
'flagged' => $i % 5 === 0,
|
||||
'attachments' => [],
|
||||
];
|
||||
}
|
||||
|
||||
// Fill Sent (MB 2) to 15
|
||||
for ($i = 1; $i <= 14; $i++) {
|
||||
$baseEmails[2][] = [
|
||||
'id' => 2000 + $i,
|
||||
'from_name' => 'Me',
|
||||
'from_email' => 'idevakk@imail.com',
|
||||
'subject' => "Re: Project Sync $i",
|
||||
'preview' => "Sounds good, let's proceed with the plan we discussed for sprint $i.",
|
||||
'body_html' => "<p>Hi team,</p><p>Update on project $i: everything is on track.</p>",
|
||||
'body_text' => "Hi team,\n\nUpdate on project $i: everything is on track.",
|
||||
'time' => 'Feb 26',
|
||||
'unread' => false,
|
||||
'flagged' => $i % 4 === 0,
|
||||
'attachments' => [],
|
||||
];
|
||||
}
|
||||
|
||||
// Fill Others (MB 3) to 15
|
||||
for ($i = 1; $i <= 14; $i++) {
|
||||
$baseEmails[3][] = [
|
||||
'id' => 3000 + $i,
|
||||
'from_name' => "System Notification $i",
|
||||
'from_email' => 'noreply@system.com',
|
||||
'subject' => "Security Alert $i: New Login",
|
||||
'preview' => "We detected a new login to your account from a new device on day $i...",
|
||||
'body_html' => "<p>A new login was detected on your account.</p><p>Location: City $i</p>",
|
||||
'body_text' => "A new login was detected on your account.\n\nLocation: City $i",
|
||||
'time' => 'Feb 25',
|
||||
'unread' => $i % 2 === 0,
|
||||
'flagged' => false,
|
||||
'attachments' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$allData = $baseEmails[$this->currentMailboxId] ?? $baseEmails[3];
|
||||
$total = count($allData);
|
||||
$this->totalPages = ceil($total / 10);
|
||||
|
||||
// Ensure page is within bounds
|
||||
if ($this->page > $this->totalPages && $this->totalPages > 0) {
|
||||
$this->page = $this->totalPages;
|
||||
}
|
||||
|
||||
return array_slice($allData, ($this->page - 1) * 10, 10);
|
||||
}
|
||||
|
||||
public function selectEmail($id)
|
||||
{
|
||||
$this->selectedEmailId = $id;
|
||||
$this->viewMode = 'text';
|
||||
$this->allowRemoteContent = false;
|
||||
|
||||
// Simulate cinematic loading
|
||||
usleep(500000); // 500ms
|
||||
}
|
||||
|
||||
public function switchMailbox($id)
|
||||
@@ -172,6 +196,7 @@ class Mailbox extends Component
|
||||
$this->currentMailboxId = $id;
|
||||
$this->selectedEmailId = null;
|
||||
$this->search = '';
|
||||
$this->page = 1;
|
||||
}
|
||||
|
||||
public function createMailbox()
|
||||
@@ -226,6 +251,7 @@ class Mailbox extends Component
|
||||
{
|
||||
if ($this->page < $this->totalPages) {
|
||||
$this->page++;
|
||||
$this->selectedEmailId = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,6 +259,7 @@ class Mailbox extends Component
|
||||
{
|
||||
if ($this->page > 1) {
|
||||
$this->page--;
|
||||
$this->selectedEmailId = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,6 +270,29 @@ class Mailbox extends Component
|
||||
$this->dispatch('qrCodeGenerated', address: $address);
|
||||
}
|
||||
|
||||
public function getProcessedContent($email)
|
||||
{
|
||||
$content = $email['body_html'];
|
||||
$isText = $this->viewMode === 'text';
|
||||
|
||||
// Fallback to HTML if text is selected but body_text is empty
|
||||
if ($isText && ! empty($email['body_text'])) {
|
||||
return trim(e($email['body_text']));
|
||||
}
|
||||
|
||||
if ($isText) {
|
||||
// If fallback occurred, we sanitize the HTML to text
|
||||
return trim(strip_tags($content));
|
||||
}
|
||||
|
||||
if (! $this->allowRemoteContent) {
|
||||
// Block remote assets by replacing src with data-src for img tags
|
||||
return preg_replace('/<img\s[^>]*?\bsrc\s*=\s*([\'"])(.*?)\1/i', '<img $2 data-blocked-src=$1$2$1 src="data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 1 1\'%3E%3C/svg%3E" class="blocked-remote-asset shadow-sm border border-white/5 opacity-50"', $content);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$currentMailbox = collect($this->activeMailboxes)->firstWhere('id', $this->currentMailboxId);
|
||||
|
||||
162
laravel_webhook_handover.md
Normal file
162
laravel_webhook_handover.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# MailOps Webhook Handover Document
|
||||
|
||||
This document provides the exact specifications needed to implement the receiving end of the MailOps email synchronization system within the Laravel application.
|
||||
|
||||
## 1. Webhook Endpoint Specification
|
||||
|
||||
The MailOps worker will push new emails to this exact endpoint on your Laravel server:
|
||||
|
||||
* **URL:** `POST https://your-laravel-app.com/api/webhooks/incoming_email`
|
||||
* **Headers:**
|
||||
* `Content-Type: application/json`
|
||||
* `Authorization: Bearer <CONFIGURED_WEBHOOK_SECRET>` (You must configure this secret in both MailOps and Laravel).
|
||||
|
||||
### A. Expected JSON Payload (With Attachments)
|
||||
|
||||
```json
|
||||
{
|
||||
"hash": "a1b2c3d4e5f6g7h8i9j0...",
|
||||
"metadata": {
|
||||
"hash": "a1b2c3d4e5f6g7h8i9j0...",
|
||||
"recipientEmail": "user@example.com",
|
||||
"recipientName": "John Doe",
|
||||
"senderEmail": "alert@service.com",
|
||||
"senderName": "Service Alerts",
|
||||
"domain": "example.com",
|
||||
"subject": "Important Notification",
|
||||
"received_at": "2026-02-26T17:35:00Z",
|
||||
"attachments": [
|
||||
{
|
||||
"filename": "invoice.pdf",
|
||||
"mimeType": "application/pdf",
|
||||
"size": 102400,
|
||||
"s3_path": "mail-attachments/2026/02/26/hash_invoice.pdf"
|
||||
}
|
||||
],
|
||||
"attachmentSize": 102400
|
||||
},
|
||||
"bodyText": "Plain text content...",
|
||||
"bodyHtml": "<html>HTML content...</html>"
|
||||
}
|
||||
```
|
||||
*(Note: `received_at` is in ISO 8601 format ending with `Z` to explicitly denote UTC. `bodyHtml` and `bodyText` are completely separated from the metadata to optimize database payload sizes).*
|
||||
|
||||
### B. Expected JSON Payload (NO Attachments)
|
||||
When an email has no attachments, the `attachments` array will be empty and `attachmentSize` will be zero. Also, depending on the email client, `bodyHtml` or `bodyText` might be `null`.
|
||||
|
||||
```json
|
||||
{
|
||||
"hash": "b2c3d4e5f6g7h8i9j0a1...",
|
||||
"metadata": {
|
||||
"hash": "b2c3d4e5f6g7h8i9j0a1...",
|
||||
"recipientEmail": "user@example.com",
|
||||
"recipientName": "",
|
||||
"senderEmail": "friend@service.com",
|
||||
"senderName": "Friend",
|
||||
"domain": "example.com",
|
||||
"subject": "Quick Question",
|
||||
"received_at": "2026-02-26T17:38:12Z",
|
||||
"attachments": [],
|
||||
"attachmentSize": 0
|
||||
},
|
||||
"bodyText": "Hey, are we still fast approaching the deadline?",
|
||||
"bodyHtml": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Laravel Implementation Checklist
|
||||
|
||||
When you switch to the Laravel project, you need to build the following:
|
||||
|
||||
### Step 1: Route & Middleware
|
||||
Define the API route and protect it with a simple Bearer token check.
|
||||
```php
|
||||
// routes/api.php
|
||||
Route::post('/webhooks/incoming_email', [EmailWebhookController::class, 'handle'])
|
||||
->middleware('verify.webhook.secret');
|
||||
```
|
||||
|
||||
### Step 2: The Controller
|
||||
The controller persists the metadata to MariaDB and the heavy body to MongoDB. **Crucially**, it also checks if the MongoDB TTL index exists, and if not, automatically creates it using the value defined in your Laravel `.env` file (e.g., `EMAIL_BODY_TTL_SECONDS=259200`).
|
||||
|
||||
```php
|
||||
// app/Http/Controllers/EmailWebhookController.php
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
public function handle(Request $request)
|
||||
{
|
||||
$payload = $request->all();
|
||||
$meta = $payload['metadata'];
|
||||
$hash = $payload['hash'];
|
||||
|
||||
// 1. Auto-Setup MongoDB TTL Index (Executes only once via Cache)
|
||||
$this->ensureMongoTtlIndexExists();
|
||||
|
||||
// 2. MariaDB: Save Metadata
|
||||
Email::updateOrCreate(
|
||||
['unique_id_hash' => $hash],
|
||||
[
|
||||
'recipient_email' => $meta['recipientEmail'],
|
||||
'sender_email' => $meta['senderEmail'],
|
||||
'subject' => $meta['subject'] ?? '',
|
||||
'is_read' => false,
|
||||
// Parse the ISO 8601 UTC timestamp format explicitly for SQL
|
||||
'received_at' => Carbon::parse($meta['received_at'])->setTimezone('UTC')->toDateTimeString(),
|
||||
// Store attachments JSON. If empty, ensure it's saved as an empty array '[]'
|
||||
'attachments' => !empty($meta['attachments']) ? json_encode($meta['attachments']) : '[]',
|
||||
'attachment_size' => $meta['attachmentSize'] ?? 0
|
||||
]
|
||||
);
|
||||
|
||||
// 3. MongoDB: Save the heavy body with TTL
|
||||
// Assuming you have the jenssegers/mongodb package installed
|
||||
RecentEmailBody::updateOrCreate(
|
||||
['unique_id_hash' => $hash],
|
||||
[
|
||||
// Handle cases where the sender only sends Text or only HTML
|
||||
'body_text' => $payload['bodyText'] ?? '',
|
||||
'body_html' => $payload['bodyHtml'] ?? '',
|
||||
'created_at' => new \MongoDB\BSON\UTCDateTime(now()->timestamp * 1000), // BSON required for TTL
|
||||
]
|
||||
);
|
||||
|
||||
return response()->json(['status' => 'success'], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the TTL index is created on the MongoDB collection.
|
||||
* Uses Laravel Cache to avoid checking the database on every single webhook.
|
||||
*/
|
||||
private function ensureMongoTtlIndexExists()
|
||||
{
|
||||
Cache::rememberForever('mongo_ttl_index_created', function () {
|
||||
// Fetch TTL from Laravel .env (Default: 72 hours / 259200 seconds)
|
||||
$ttlSeconds = (int) env('EMAIL_BODY_TTL_SECONDS', 259200);
|
||||
|
||||
$collection = DB::connection('mongodb')->getCollection('recent_email_bodies');
|
||||
|
||||
// Background creation prevents locking the database during webhook execution
|
||||
$collection->createIndex(
|
||||
['created_at' => 1],
|
||||
[
|
||||
'expireAfterSeconds' => $ttlSeconds,
|
||||
'background' => true,
|
||||
'name' => 'ttl_created_at_index' // Named index prevents duplicate recreation errors
|
||||
]
|
||||
);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Resiliency Notes
|
||||
|
||||
* **Idempotency:** The MailOps worker might retry a webhook if a network timeout occurs even after Laravel successfully saved it. Your Laravel code MUST use `updateOrCreate` or `INSERT IGNORE` (like the example above) so it doesn't create duplicate emails if the same payload hash is received twice.
|
||||
* **Timeouts:** The MailOps worker expects a response within 5 to 10 seconds. Do not perform long-running synchronous tasks (like connecting to external APIs or sending heavy push notifications) inside the webhook controller. Dispatch those to a Laravel Queue instead.
|
||||
@@ -1,3 +1,5 @@
|
||||
<svg width="166" height="166" viewBox="0 0 166 166" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M162.041 38.7592C162.099 38.9767 162.129 39.201 162.13 39.4264V74.4524C162.13 74.9019 162.011 75.3435 161.786 75.7325C161.561 76.1216 161.237 76.4442 160.847 76.6678L131.462 93.5935V127.141C131.462 128.054 130.977 128.897 130.186 129.357L68.8474 164.683C68.707 164.763 68.5538 164.814 68.4007 164.868C68.3432 164.887 68.289 164.922 68.2284 164.938C67.7996 165.051 67.3489 165.051 66.9201 164.938C66.8499 164.919 66.7861 164.881 66.7191 164.855C66.5787 164.804 66.4319 164.76 66.2979 164.683L4.97219 129.357C4.58261 129.133 4.2589 128.81 4.0337 128.421C3.8085 128.032 3.68976 127.591 3.68945 127.141L3.68945 22.0634C3.68945 21.8336 3.72136 21.6101 3.7788 21.393C3.79794 21.3196 3.84262 21.2526 3.86814 21.1791C3.91601 21.0451 3.96068 20.9078 4.03088 20.7833C4.07874 20.7003 4.14894 20.6333 4.20638 20.5566C4.27977 20.4545 4.34678 20.3491 4.43293 20.2598C4.50632 20.1863 4.60205 20.1321 4.68501 20.0682C4.77755 19.9916 4.86051 19.9086 4.96581 19.848L35.6334 2.18492C36.0217 1.96139 36.4618 1.84375 36.9098 1.84375C37.3578 1.84375 37.7979 1.96139 38.1862 2.18492L68.8506 19.848H68.857C68.9591 19.9118 69.0452 19.9916 69.1378 20.065C69.2207 20.1289 69.3133 20.1863 69.3867 20.2566C69.476 20.3491 69.5398 20.4545 69.6164 20.5566C69.6707 20.6333 69.7441 20.7003 69.7887 20.7833C69.8621 20.911 69.9036 21.0451 69.9546 21.1791C69.9802 21.2526 70.0248 21.3196 70.044 21.3962C70.1027 21.6138 70.1328 21.8381 70.1333 22.0634V87.6941L95.686 72.9743V39.4232C95.686 39.1997 95.7179 38.9731 95.7753 38.7592C95.7977 38.6826 95.8391 38.6155 95.8647 38.5421C95.9157 38.408 95.9604 38.2708 96.0306 38.1463C96.0785 38.0633 96.1487 37.9962 96.2029 37.9196C96.2795 37.8175 96.3433 37.7121 96.4326 37.6227C96.506 37.5493 96.5986 37.495 96.6815 37.4312C96.7773 37.3546 96.8602 37.2716 96.9623 37.2109L127.633 19.5479C128.021 19.324 128.461 19.2062 128.91 19.2062C129.358 19.2062 129.798 19.324 130.186 19.5479L160.85 37.2109C160.959 37.2748 161.042 37.3546 161.137 37.428C161.217 37.4918 161.31 37.5493 161.383 37.6195C161.473 37.7121 161.536 37.8175 161.613 37.9196C161.67 37.9962 161.741 38.0633 161.785 38.1463C161.859 38.2708 161.9 38.408 161.951 38.5421C161.98 38.6155 162.021 38.6826 162.041 38.7592ZM157.018 72.9743V43.8477L146.287 50.028L131.462 58.5675V87.6941L157.021 72.9743H157.018ZM126.354 125.663V96.5176L111.771 104.85L70.1301 128.626V158.046L126.354 125.663ZM8.80126 26.4848V125.663L65.0183 158.043V128.629L35.6494 112L35.6398 111.994L35.6271 111.988C35.5281 111.93 35.4452 111.847 35.3526 111.777C35.2729 111.713 35.1803 111.662 35.1101 111.592L35.1038 111.582C35.0208 111.502 34.9634 111.403 34.8932 111.314C34.8293 111.228 34.7528 111.154 34.7017 111.065L34.6985 111.055C34.6411 110.96 34.606 110.845 34.5645 110.736C34.523 110.64 34.4688 110.551 34.4432 110.449C34.4113 110.328 34.4049 110.197 34.3922 110.072C34.3794 109.976 34.3539 109.881 34.3539 109.785V109.778V41.2045L19.5322 32.6619L8.80126 26.4848ZM36.913 7.35007L11.3635 22.0634L36.9066 36.7768L62.4529 22.0602L36.9066 7.35007H36.913ZM50.1999 99.1736L65.0215 90.6374V26.4848L54.2906 32.6651L39.4657 41.2045V105.357L50.1999 99.1736ZM128.91 24.713L103.363 39.4264L128.91 54.1397L154.453 39.4232L128.91 24.713ZM126.354 58.5675L111.529 50.028L100.798 43.8477V72.9743L115.619 81.5106L126.354 87.6941V58.5675ZM67.5711 124.205L105.042 102.803L123.772 92.109L98.2451 77.4053L68.8538 94.3341L42.0663 109.762L67.5711 124.205Z" fill="#FF2D20"/>
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="32" height="32" rx="8" fill="#18181b"/>
|
||||
<path d="M8 24L24 8M8 24H18M24 8H14" stroke="#ec4899" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="26" cy="6" r="2" fill="#10b981"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 334 B |
BIN
public/images/logo-dark.webp
Normal file
BIN
public/images/logo-dark.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
103
resources/views/components/bento/confirm-modal.blade.php
Normal file
103
resources/views/components/bento/confirm-modal.blade.php
Normal file
@@ -0,0 +1,103 @@
|
||||
@props([
|
||||
'name' => 'confirm-modal',
|
||||
])
|
||||
|
||||
<div x-show="show"
|
||||
x-data="{
|
||||
show: false,
|
||||
title: '',
|
||||
message: '',
|
||||
confirmLabel: 'Confirm',
|
||||
type: 'danger',
|
||||
action: null,
|
||||
open(data) {
|
||||
this.title = data.title || 'Are you sure?';
|
||||
this.message = data.message || 'This action cannot be undone.';
|
||||
this.confirmLabel = data.confirmLabel || 'Confirm';
|
||||
this.type = data.type || 'danger';
|
||||
this.action = data.action || null;
|
||||
this.show = true;
|
||||
},
|
||||
confirm() {
|
||||
if (this.action) {
|
||||
this.action();
|
||||
}
|
||||
this.show = false;
|
||||
}
|
||||
}"
|
||||
x-on:open-{{ $name }}.window="open($event.detail)"
|
||||
class="fixed inset-0 z-[110] flex items-center justify-center p-4 lg:p-8"
|
||||
style="display: none;"
|
||||
x-cloak>
|
||||
|
||||
<!-- Backdrop -->
|
||||
<div x-show="show"
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
@click="show = false"
|
||||
class="absolute inset-0 bg-zinc-950/80 backdrop-blur-xl"></div>
|
||||
|
||||
<!-- Modal Card -->
|
||||
<div x-show="show"
|
||||
x-transition:enter="transition ease-out duration-500"
|
||||
x-transition:enter-start="opacity-0 scale-95 translate-y-8"
|
||||
x-transition:enter-end="opacity-100 scale-100 translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-300"
|
||||
x-transition:leave-start="opacity-100 scale-100 translate-y-0"
|
||||
x-transition:leave-end="opacity-0 scale-95 translate-y-8"
|
||||
class="w-full max-w-[400px] bg-zinc-900 border border-white/10 rounded-[32px] p-8 relative overflow-hidden shadow-2xl">
|
||||
|
||||
<!-- Background Glow based on type -->
|
||||
<div class="absolute top-0 left-1/2 -translate-x-1/2 w-64 h-64 rounded-full blur-[80px] -z-10 opacity-20"
|
||||
:class="{
|
||||
'bg-rose-500': type === 'danger',
|
||||
'bg-blue-500': type === 'info',
|
||||
'bg-amber-500': type === 'warning',
|
||||
'bg-emerald-500': type === 'success'
|
||||
}"></div>
|
||||
|
||||
<div class="text-center">
|
||||
<!-- Icon -->
|
||||
<div class="w-16 h-16 rounded-2xl flex items-center justify-center mx-auto mb-6 shadow-xl"
|
||||
:class="{
|
||||
'bg-rose-500/10 text-rose-500': type === 'danger',
|
||||
'bg-blue-500/10 text-blue-500': type === 'info',
|
||||
'bg-amber-500/10 text-amber-500': type === 'warning',
|
||||
'bg-emerald-500/10 text-emerald-500': type === 'success'
|
||||
}">
|
||||
<!-- Danger Icon -->
|
||||
<svg x-show="type === 'danger'" class="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
|
||||
<!-- Warning Icon -->
|
||||
<svg x-show="type === 'warning'" class="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
|
||||
<!-- Info Icon -->
|
||||
<svg x-show="type === 'info'" class="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
<!-- Success Icon -->
|
||||
<svg x-show="type === 'success'" class="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg>
|
||||
</div>
|
||||
|
||||
<h3 class="text-2xl font-black text-white mb-2 tracking-tight uppercase italic" x-text="title"></h3>
|
||||
<p class="text-xs font-bold text-zinc-500 uppercase tracking-widest leading-relaxed mb-8 px-4" x-text="message"></p>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<button @click="show = false"
|
||||
class="py-4 rounded-2xl bg-white/5 border border-white/10 text-zinc-400 font-black text-[10px] uppercase tracking-[0.2em] hover:bg-white/10 hover:text-white transition-all">
|
||||
Cancel
|
||||
</button>
|
||||
<button @click="confirm()"
|
||||
class="py-4 rounded-2xl font-black text-[10px] uppercase tracking-[0.2em] shadow-xl transition-all"
|
||||
:class="{
|
||||
'bg-rose-600 text-white hover:bg-rose-500 shadow-rose-900/20': type === 'danger',
|
||||
'bg-blue-600 text-white hover:bg-blue-500 shadow-blue-900/20': type === 'info',
|
||||
'bg-amber-600 text-white hover:bg-amber-500 shadow-amber-900/20': type === 'warning',
|
||||
'bg-emerald-600 text-white hover:bg-emerald-500 shadow-emerald-900/20': type === 'success'
|
||||
}"
|
||||
x-text="confirmLabel">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,5 +1,5 @@
|
||||
<div>
|
||||
<x-slot:title>{{ $title }} — Imail</x-slot:title>
|
||||
<x-slot:title>{{ $title }} — Zemail</x-slot:title>
|
||||
|
||||
<div class="flex-1">
|
||||
<!-- Reusable Cinematic Header -->
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
Common <span class="text-pink-500">questions.</span>
|
||||
</h2>
|
||||
<p class="text-zinc-400 text-sm md:text-lg leading-relaxed">
|
||||
Everything you need to know about Imail, privacy, and temporary communication.
|
||||
Everything you need to know about Zemail, privacy, and temporary communication.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
x-collapse
|
||||
class="px-6 pb-6">
|
||||
<p class="text-sm text-zinc-400 leading-relaxed">
|
||||
Yes! Imail uses a sophisticated backend to route communications through temporary, high-reputation Gmail and Outlook nodes. This ensures that you can bypass "Disposable Email" filters while maintaining 100% privacy.
|
||||
Yes! Zemail uses a sophisticated backend to route communications through temporary, high-reputation Gmail and Outlook nodes. This ensures that you can bypass "Disposable Email" filters while maintaining 100% privacy.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -66,7 +66,7 @@
|
||||
:class="active === 3 ? 'border-pink-500/30 bg-zinc-900/50 shadow-[0_0_30px_rgba(236,72,153,0.05)]' : 'hover:border-white/10'">
|
||||
<button @click="active = active === 3 ? null : 3"
|
||||
class="w-full px-6 py-5 flex items-center justify-between text-left transition-colors">
|
||||
<span class="font-bold text-zinc-100 group-hover:text-white">Is Imail secure for sensitive data?</span>
|
||||
<span class="font-bold text-zinc-100 group-hover:text-white">Is Zemail secure for sensitive data?</span>
|
||||
<svg class="w-5 h-5 text-zinc-500 transition-transform duration-300"
|
||||
:class="active === 3 ? 'rotate-180 text-pink-500' : ''"
|
||||
fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
@@ -140,7 +140,7 @@
|
||||
x-collapse
|
||||
class="px-6 pb-6">
|
||||
<p class="text-sm text-zinc-400 leading-relaxed">
|
||||
No. Imail is a 100% web-based service. It is accessible across all modern browsers and devices (Desktop, Mobile, Tablet) without the need for any apps or browser extensions.
|
||||
No. Zemail is a 100% web-based service. It is accessible across all modern browsers and devices (Desktop, Mobile, Tablet) without the need for any apps or browser extensions.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<div class="grid grid-cols-1 md:grid-cols-12 gap-12 mb-16">
|
||||
<!-- Brand & Status -->
|
||||
<div class="col-span-1 md:col-span-5">
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<h2 class="text-2xl font-black tracking-tighter text-white">Imail</h2>
|
||||
<div class="flex items-center gap-6 mb-6">
|
||||
<x-bento.logo size="sm" />
|
||||
<div class="flex items-center gap-2 px-2 py-0.5 rounded-md bg-emerald-500/10 border border-emerald-500/20">
|
||||
<div class="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-[pulse_1.5s_infinite]"></div>
|
||||
<span class="text-[9px] font-bold text-emerald-500 uppercase tracking-widest">Systems Operational</span>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
Everything you need, <span class="text-zinc-500">nothing you don't.</span>
|
||||
</h2>
|
||||
<p class="text-zinc-400 max-w-2xl text-sm md:text-lg leading-relaxed">
|
||||
Imail is built for the modern web. From instant API access to premium Gmail domains,
|
||||
Zemail is built for the modern web. From instant API access to premium Gmail domains,
|
||||
we've automated the friction out of temporary email management.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-pink-400 opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-2 w-2 bg-pink-500"></span>
|
||||
</span>
|
||||
Imail Beta Live
|
||||
Zemail Beta Live
|
||||
</div>
|
||||
|
||||
<h1 class="text-4xl md:text-6xl lg:text-7xl font-bold tracking-tight text-transparent bg-clip-text bg-gradient-to-br from-white to-zinc-500 mb-6 opacity-0 translate-y-8 leading-tight" x-init="gsap.to($el, {opacity: 1, y: 0, duration: 1, delay: 0.1, ease: 'power3.out'})">
|
||||
@@ -34,7 +34,7 @@
|
||||
</span>
|
||||
</a>
|
||||
<a href="/api-docs" class="inline-flex items-center justify-center px-8 py-3.5 text-base font-semibold text-white bg-white/5 border border-white/10 rounded-full transition-all hover:bg-white/10">
|
||||
Imail in 100 seconds <svg class="w-4 h-4 ml-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
||||
Zemail in 100 seconds <svg class="w-4 h-4 ml-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -163,10 +163,10 @@
|
||||
{
|
||||
id: 1,
|
||||
sender: 'admin@imail.local',
|
||||
subject: 'Hello World! I am Imail',
|
||||
snippet: 'Click me to discover what makes Imail so special...',
|
||||
subject: 'Hello World! I am Zemail',
|
||||
snippet: 'Click me to discover what makes Zemail so special...',
|
||||
time: '1m ago',
|
||||
content: 'Welcome to Imail! ✨<br><br>We provide instantly created, highly reliable temporary email addresses that just work.<br><br><strong>Features:</strong><br><ul class="list-disc pl-5 mt-2 space-y-1"><li>Instant Inbox creation without sign-up.</li><li>Access to Premium & Generic Domains.</li><li>Real-time Websocket syncing for 0 delay.</li><li>Developer APIs ready for E2E automated testing.</li></ul><br>Enjoy your clean, spam-free inbox today!'
|
||||
content: 'Welcome to Zemail! ✨<br><br>We provide instantly created, highly reliable temporary email addresses that just work.<br><br><strong>Features:</strong><br><ul class="list-disc pl-5 mt-2 space-y-1"><li>Instant Inbox creation without sign-up.</li><li>Access to Premium & Generic Domains.</li><li>Real-time Websocket syncing for 0 delay.</li><li>Developer APIs ready for E2E automated testing.</li></ul><br>Enjoy your clean, spam-free inbox today!'
|
||||
}
|
||||
],
|
||||
counter: 2,
|
||||
@@ -186,7 +186,7 @@
|
||||
subject: subjects[idx],
|
||||
snippet: snippets[idx],
|
||||
time: 'Just now',
|
||||
content: `This is a simulated email received via our ultra-fast websocket delivery system.<br><br><strong>Action:</strong> ${subjects[idx]}<br><strong>Time Received:</strong> ${new Date().toLocaleTimeString()}<br><br>In a real Imail inbox, your emails appear instantly like this as soon as they hit our SMTP servers, bypassing typical polling delays.`
|
||||
content: `This is a simulated email received via our ultra-fast websocket delivery system.<br><br><strong>Action:</strong> ${subjects[idx]}<br><strong>Time Received:</strong> ${new Date().toLocaleTimeString()}<br><br>In a real Zemail inbox, your emails appear instantly like this as soon as they hit our SMTP servers, bypassing typical polling delays.`
|
||||
});
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
50
resources/views/components/bento/logo.blade.php
Normal file
50
resources/views/components/bento/logo.blade.php
Normal file
@@ -0,0 +1,50 @@
|
||||
@props([
|
||||
'size' => 'md', // sm, md, lg
|
||||
'withText' => true,
|
||||
'class' => ''
|
||||
])
|
||||
|
||||
@php
|
||||
$dimensions = match($size) {
|
||||
'sm' => ['box' => 'w-8 h-8', 'icon' => 'w-4 h-4', 'text' => 'text-base'],
|
||||
'md' => ['box' => 'w-10 h-10', 'icon' => 'w-5 h-5', 'text' => 'text-xl'],
|
||||
'lg' => ['box' => 'w-12 h-12', 'icon' => 'w-6 h-6', 'text' => 'text-2xl'],
|
||||
default => ['box' => 'w-10 h-10', 'icon' => 'w-5 h-5', 'text' => 'text-xl'],
|
||||
};
|
||||
@endphp
|
||||
|
||||
<div x-data="{ showText: {{ $withText ? 'true' : 'false' }} }" {{ $attributes }} class="flex items-center gap-3 {{ $class }}">
|
||||
<!-- Logo Symbol -->
|
||||
<div class="relative group">
|
||||
<!-- Outer Glow -->
|
||||
<div class="absolute -inset-2 bg-gradient-to-tr from-pink-500/20 to-emerald-500/20 rounded-2xl blur-lg opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
|
||||
|
||||
<!-- Icon Container -->
|
||||
<div class="{{ $dimensions['box'] }} bg-zinc-900 border border-white/10 rounded-xl flex items-center justify-center relative overflow-hidden shadow-xl">
|
||||
<!-- Subtle Mesh Background -->
|
||||
<div class="absolute inset-0 bg-[radial-gradient(circle_at_50%_0%,rgba(236,72,153,0.1),transparent)]"></div>
|
||||
|
||||
<!-- Stylized "Z" / Envelope Fold -->
|
||||
<svg class="{{ $dimensions['icon'] }} text-transparent" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="logo-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#ec4899" />
|
||||
<stop offset="100%" style="stop-color:#be185d" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M4 19L20 5M4 19H14M20 5H10" stroke="url(#logo-gradient)" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4 5L20 19" stroke="white" stroke-opacity="0.1" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
<!-- Activity Pulsar (Top Right) -->
|
||||
<div class="absolute top-1.5 right-1.5 w-1.5 h-1.5 bg-emerald-500 rounded-full shadow-[0_0_8px_rgba(16,185,129,0.5)]">
|
||||
<div class="absolute inset-0 bg-emerald-500 rounded-full animate-ping opacity-75"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center" x-show="showText" x-transition:opacity>
|
||||
<span class="{{ $dimensions['text'] }} font-black tracking-tighter text-white italic uppercase">zemail</span>
|
||||
<span class="{{ $dimensions['text'] }} font-black tracking-tighter text-pink-500 leading-none italic uppercase">.me</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -8,11 +8,8 @@
|
||||
<!-- Left Side: Logo + Links -->
|
||||
<div class="flex items-center gap-8">
|
||||
<!-- Logo -->
|
||||
<a href="/" class="flex items-center gap-2 group z-50">
|
||||
<svg class="w-6 h-6 text-white group-hover:text-pink-500 transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<span class="text-lg font-bold tracking-wide text-white">Imail</span>
|
||||
<a href="/" class="z-50">
|
||||
<x-bento.logo size="sm" />
|
||||
</a>
|
||||
|
||||
<!-- Desktop Links -->
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
Trusted by <span class="text-emerald-500">thousands</span> of devs.
|
||||
</h2>
|
||||
<p class="text-zinc-400 max-w-2xl text-sm md:text-lg leading-relaxed">
|
||||
Join the community of privacy-first developers, QA testers, and power users who rely on Imail every day.
|
||||
Join the community of privacy-first developers, QA testers, and power users who rely on Zemail every day.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-zinc-400 text-sm leading-relaxed italic">
|
||||
"Imail is my go-to for checking transactional emails on our staging environment. The attachments scanning gives me peace of mind when testing file delivery systems."
|
||||
"Zemail is my go-to for checking transactional emails on our staging environment. The attachments scanning gives me peace of mind when testing file delivery systems."
|
||||
</p>
|
||||
<div class="mt-6 flex gap-1">
|
||||
@for($i=0; $i<5; $i++)
|
||||
@@ -138,7 +138,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-zinc-400 text-sm leading-relaxed italic">
|
||||
"Reliability is key for our automation suite, and Imail hasn't let us down. The speed of reception is nearly identical to our production Gmail setup."
|
||||
"Reliability is key for our automation suite, and Zemail hasn't let us down. The speed of reception is nearly identical to our production Gmail setup."
|
||||
</p>
|
||||
<div class="mt-6 flex gap-1">
|
||||
@for($i=0; $i<5; $i++)
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ $title ?? 'Mailbox — Imail' }}</title>
|
||||
<title>{{ $title ?? 'Mailbox — Zemail' }}</title>
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ $title ?? 'Imail — Instant Disposable Gmail & Temporary Email' }}</title>
|
||||
<title>{{ $title ?? 'Zemail — Instant Disposable Gmail & Temporary Email' }}</title>
|
||||
<link rel="icon" href="/favicon.ico" sizes="any">
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
||||
|
||||
@@ -20,9 +20,12 @@
|
||||
});
|
||||
}
|
||||
}"
|
||||
x-init="$watch('selectedId', value => { if(value && window.innerWidth < 1024) mobileView = 'detail' })"
|
||||
x-init="$watch('selectedId', value => { if(value && window.innerWidth < 1024) mobileView = 'detail' }); $watch('mobileView', value => { if(value === 'list' && window.innerWidth < 1024) selectedId = null })"
|
||||
@resize.window="if (window.innerWidth >= 1280) sidebarOpen = true">
|
||||
|
||||
<!-- Global Confirmation Modal -->
|
||||
<x-bento.confirm-modal />
|
||||
|
||||
<!-- Mobile Sidebar Backdrop -->
|
||||
<div x-show="sidebarOpen"
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
@@ -42,14 +45,10 @@
|
||||
'xl:w-64': sidebarOpen && window.innerWidth >= 1280,
|
||||
'xl:w-20': !sidebarOpen && window.innerWidth >= 1280
|
||||
}">
|
||||
<!-- Sidebar Header -->
|
||||
<div class="h-16 flex items-center justify-between px-6 border-b border-white/5">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded-lg bg-pink-500 flex items-center justify-center text-white shadow-[0_0_15px_rgba(236,72,153,0.3)]">
|
||||
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /></svg>
|
||||
</div>
|
||||
<span class="font-bold text-lg tracking-tight text-white transition-opacity" :class="sidebarOpen ? 'opacity-100' : 'opacity-0 hidden'">Imail</span>
|
||||
</div>
|
||||
<a href="/" class="flex items-center gap-3 hover:opacity-80 transition-opacity">
|
||||
<x-bento.logo size="sm" x-data="{ showText: sidebarOpen }" />
|
||||
</a>
|
||||
|
||||
<!-- Mobile Close Button -->
|
||||
<button @click="sidebarOpen = false" class="xl:hidden p-1.5 rounded-lg hover:bg-white/5 text-zinc-500 hover:text-white transition-all">
|
||||
@@ -93,8 +92,13 @@
|
||||
title="Copy Address">
|
||||
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" /></svg>
|
||||
</button>
|
||||
<button wire:click="deleteMailbox({{ $currentMailbox['id'] }})"
|
||||
wire:confirm="Are you sure you want to delete this session? All emails will be lost."
|
||||
<button @click="$dispatch('open-confirm-modal', {
|
||||
title: 'Burn Mailbox?',
|
||||
message: 'Are you sure you want to delete this mailbox? All emails will be permanently lost.',
|
||||
confirmLabel: 'Burn Now',
|
||||
type: 'danger',
|
||||
action: () => $wire.deleteMailbox({{ $currentMailbox['id'] }})
|
||||
})"
|
||||
class="p-1.5 rounded-lg bg-rose-500/10 text-rose-500/60 hover:text-rose-500 hover:bg-rose-500/20 transition-all"
|
||||
title="Delete Session">
|
||||
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
|
||||
@@ -209,13 +213,13 @@
|
||||
</div>
|
||||
|
||||
<!-- List Content -->
|
||||
<div class="flex-1 overflow-y-auto divide-y divide-white/5 scrollbar-hide">
|
||||
<div class="flex-1 overflow-y-auto divide-y divide-white/5 scrollbar-hide" x-ref="listContainer">
|
||||
@foreach($emails as $email)
|
||||
<div @click="$wire.selectEmail({{ $email['id'] }}); mobileView = 'detail'"
|
||||
<div wire:key="email-{{ $email['id'] }}"
|
||||
@click="$wire.selectEmail({{ $email['id'] }}); mobileView = 'detail'"
|
||||
class="p-5 cursor-pointer transition-all relative group"
|
||||
:class="selectedId === {{ $email['id'] }} ? 'bg-pink-500/5' : 'hover:bg-white/[0.02]'">
|
||||
<!-- Active Indicator -->
|
||||
<div class="absolute left-0 top-0 bottom-0 w-1 bg-pink-500 transition-transform duration-300"
|
||||
<div class="absolute left-0 top-0 bottom-0 w-1 bg-pink-500 scale-y-0 transition-transform duration-300"
|
||||
:class="selectedId === {{ $email['id'] }} ? 'scale-y-100' : 'scale-y-0'"></div>
|
||||
|
||||
<div class="flex items-start justify-between gap-4 mb-2">
|
||||
@@ -253,6 +257,7 @@
|
||||
<!-- Sticky Pagination -->
|
||||
<div class="h-14 flex items-center justify-between px-4 border-t border-white/5 bg-zinc-950/40 backdrop-blur-xl shrink-0">
|
||||
<button wire:click="previousPage"
|
||||
@click="$refs.listContainer.scrollTop = 0"
|
||||
class="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-white/5 border border-white/5 text-[10px] font-bold text-zinc-400 hover:text-white hover:bg-white/10 transition-all disabled:opacity-30 disabled:pointer-events-none uppercase tracking-widest"
|
||||
{{ $page <= 1 ? 'disabled' : '' }}>
|
||||
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" /></svg>
|
||||
@@ -266,6 +271,7 @@
|
||||
</div>
|
||||
|
||||
<button wire:click="nextPage"
|
||||
@click="$refs.listContainer.scrollTop = 0"
|
||||
class="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-white/5 border border-white/5 text-[10px] font-bold text-zinc-400 hover:text-white hover:bg-white/10 transition-all disabled:opacity-30 disabled:pointer-events-none uppercase tracking-widest"
|
||||
{{ $page >= $totalPages ? 'disabled' : '' }}>
|
||||
Next
|
||||
@@ -324,9 +330,33 @@
|
||||
</div>
|
||||
|
||||
<!-- Email Detail Column -->
|
||||
<main class="flex-1 flex flex-col min-w-0 bg-zinc-950/50 backdrop-blur-md relative z-10 transition-all duration-300"
|
||||
<main class="flex-1 flex flex-col min-w-0 bg-zinc-950/50 backdrop-blur-md relative z-10 transition-all duration-300 h-full overflow-hidden"
|
||||
:class="{'hidden lg:flex': mobileView === 'list' || !selectedId}">
|
||||
|
||||
<!-- Cinematic Loading Overlay -->
|
||||
<div wire:loading.flex wire:target="selectEmail" class="absolute inset-0 z-[100] bg-zinc-950/60 backdrop-blur-xl items-center justify-center">
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<div class="w-20 h-20 relative mb-8">
|
||||
<div class="absolute inset-0 border-4 border-pink-500/10 rounded-full"></div>
|
||||
<div class="absolute inset-0 border-4 border-pink-500 border-t-transparent animate-spin rounded-full shadow-[0_0_20px_rgba(236,72,153,0.2)]"></div>
|
||||
<div class="absolute inset-4 bg-pink-500/20 rounded-full blur-2xl animate-pulse"></div>
|
||||
<div class="absolute inset-0 flex items-center justify-center">
|
||||
<svg class="w-8 h-8 text-pink-500 animate-pulse" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-center">
|
||||
<span class="text-[10px] font-black text-white uppercase tracking-[0.4em] mb-2 animate-pulse">Establishing Secure Channel</span>
|
||||
<div class="flex gap-1">
|
||||
<div class="w-1 h-1 rounded-full bg-pink-500 animate-bounce [animation-delay:-0.3s]"></div>
|
||||
<div class="w-1 h-1 rounded-full bg-pink-500 animate-bounce [animation-delay:-0.15s]"></div>
|
||||
<div class="w-1 h-1 rounded-full bg-pink-500 animate-bounce"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@php $currentEmail = $selectedEmailId ? collect($emails)->firstWhere('id', $selectedEmailId) : null; @endphp
|
||||
@if($currentEmail)
|
||||
|
||||
@@ -347,15 +377,36 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="h-6 w-px bg-white/5"></div>
|
||||
<button wire:click="deleteEmail({{ $selectedEmailId }})"
|
||||
<button @click="$dispatch('open-confirm-modal', {
|
||||
title: 'Burn Email?',
|
||||
message: 'Are you sure you want to delete this email? This action is permanent.',
|
||||
confirmLabel: 'Burn Now',
|
||||
type: 'danger',
|
||||
action: () => $wire.deleteEmail({{ $selectedEmailId }})
|
||||
})"
|
||||
class="p-2 rounded-xl bg-white/5 border border-white/5 text-zinc-400 hover:text-rose-500 hover:bg-rose-500/10 transition-all" title="Delete Email">
|
||||
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Privacy Toggles -->
|
||||
<div class="ml-auto flex items-center p-1 bg-white/5 border border-white/5 rounded-xl">
|
||||
<button wire:click="$set('viewMode', 'text')"
|
||||
class="px-3 py-1.5 rounded-lg text-[10px] font-bold uppercase tracking-widest transition-all"
|
||||
:class="$wire.viewMode === 'text' ? 'bg-zinc-800 text-white shadow-lg' : 'text-zinc-500 hover:text-zinc-300'">
|
||||
Text
|
||||
</button>
|
||||
<button wire:click="$set('viewMode', 'html')"
|
||||
class="px-3 py-1.5 rounded-lg text-[10px] font-bold uppercase tracking-widest transition-all"
|
||||
:class="$wire.viewMode === 'html' ? 'bg-zinc-800 text-white shadow-lg' : 'text-zinc-500 hover:text-zinc-300'">
|
||||
HTML
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Detail Content -->
|
||||
<div class="flex-1 overflow-y-auto p-8 lg:p-12 scrollbar-hide">
|
||||
<div class="flex-1 relative overflow-hidden">
|
||||
<div class="h-full overflow-y-auto p-8 lg:p-12 scrollbar-hide" wire:loading.remove wire:target="selectEmail">
|
||||
<div class="max-w-3xl">
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-12">
|
||||
<div class="flex items-center gap-4">
|
||||
@@ -376,9 +427,57 @@
|
||||
{{ $currentEmail['subject'] }}
|
||||
</h1>
|
||||
|
||||
<div class="prose prose-invert max-w-none text-zinc-400 leading-relaxed space-y-4 font-medium mb-12">
|
||||
{!! $currentEmail['content'] !!}
|
||||
@if($viewMode === 'html')
|
||||
<div class="mb-8 p-4 rounded-2xl border transition-all duration-500 @if(!$allowRemoteContent) bg-amber-500/5 border-amber-500/20 @else bg-emerald-500/5 border-emerald-500/20 @endif flex flex-col sm:flex-row items-center justify-between gap-4 group">
|
||||
<div class="flex items-center gap-3">
|
||||
@if(!$allowRemoteContent)
|
||||
<div class="w-10 h-10 rounded-xl bg-amber-500/10 text-amber-500 flex items-center justify-center shrink-0">
|
||||
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs font-bold text-amber-500 uppercase tracking-widest leading-none mb-1">Remote assets blocked</div>
|
||||
<p class="text-[10px] text-zinc-500 font-medium">For your privacy, Zemail has disabled automatic loading of remote images.</p>
|
||||
</div>
|
||||
@else
|
||||
<div class="w-10 h-10 rounded-xl bg-emerald-500/10 text-emerald-500 flex items-center justify-center shrink-0">
|
||||
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs font-bold text-emerald-500 uppercase tracking-widest leading-none mb-1">Remote content enabled</div>
|
||||
<p class="text-[10px] text-zinc-500 font-medium">Images and remote resources are currently active for this email.</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if(!$allowRemoteContent)
|
||||
<button wire:click="$set('allowRemoteContent', true)"
|
||||
class="px-4 py-2 rounded-xl bg-amber-500 text-zinc-950 font-black text-[10px] uppercase tracking-wider hover:bg-amber-400 transition-all flex items-center gap-2">
|
||||
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>
|
||||
Load Content
|
||||
</button>
|
||||
@else
|
||||
<button wire:click="$set('allowRemoteContent', false)"
|
||||
class="px-4 py-2 rounded-xl bg-zinc-800 text-zinc-400 font-bold text-[10px] uppercase tracking-wider hover:text-white transition-all flex items-center gap-2 border border-white/5">
|
||||
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" /></svg>
|
||||
Re-Block
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($viewMode === 'text' && empty($currentEmail['body_text']))
|
||||
<div class="mb-8 p-4 rounded-2xl bg-zinc-900 border border-white/5 flex items-center gap-4">
|
||||
<div class="w-10 h-10 rounded-xl bg-white/5 text-zinc-400 flex items-center justify-center shrink-0">
|
||||
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-[10px] font-bold text-zinc-300 uppercase tracking-widest leading-none mb-1">Plain text version missing</div>
|
||||
<p class="text-[9px] text-zinc-500 font-medium">Viewing sanitized HTML version instead.</p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="prose prose-invert max-w-none text-zinc-400 leading-relaxed font-medium mb-12 {{ $viewMode === 'text' ? 'whitespace-pre-wrap font-mono text-[13px] shadow-[inset_0_20px_50px_rgba(0,0,0,0.1)] p-8 bg-zinc-900/30 rounded-3xl border border-white/5 tracking-tight' : 'space-y-4' }}">{!! $this->getProcessedContent($currentEmail) !!}</div>
|
||||
|
||||
@if(count($currentEmail['attachments']) > 0)
|
||||
<div class="mt-12 pt-8 border-t border-white/5">
|
||||
@@ -421,9 +520,10 @@
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<!-- Empty State -->
|
||||
<div class="flex-1 flex flex-col items-center justify-center p-12 text-center">
|
||||
<div class="flex-1 flex flex-col items-center justify-center p-12 text-center" wire:loading.remove wire:target="selectEmail">
|
||||
<div class="w-24 h-24 rounded-3xl bg-zinc-900 border border-white/5 flex items-center justify-center text-zinc-700 mb-8 relative">
|
||||
<div class="absolute inset-0 bg-pink-500/5 rounded-3xl blur-2xl"></div>
|
||||
<svg class="w-12 h-12 relative z-10" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /></svg>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="legal-content">
|
||||
<h2>1. Data Collection</h2>
|
||||
<p>
|
||||
Imail is designed to be privacy-first. We do not collect personal information like your name, physical address, or phone number. When you create a disposable inbox, we only generate a temporary identifier to route your mail.
|
||||
Zemail is designed to be privacy-first. We do not collect personal information like your name, physical address, or phone number. When you create a disposable inbox, we only generate a temporary identifier to route your mail.
|
||||
</p>
|
||||
<ul>
|
||||
<li>No user registration required</li>
|
||||
|
||||
Reference in New Issue
Block a user